Files
go-telegram/api/methods_gen_test.go
T
lukaszraczylo 140ea13bde fix(genapi): scope method-param enums per method, dedupe case-colliding enum consts
Bot API update added RichBlockListItem.type values (a/A/i/I/1) that
Pascal-case to identical const idents, and answerGuestQuery's non-enum
'result' param picked up answerChatJoinRequestQuery's Result enum via
the shared parent="" enum-plan key.

- key method params as method:<name> in the enum plan byField map
- enumDecl.ConstName resolves case collisions with Lower/Upper prefix
- regenerate api/ from 2026-06-11 snapshot
2026-06-12 00:03:45 +01:00

25273 lines
892 KiB
Go

// Code generated by cmd/genapi. DO NOT EDIT.
//go:build !ignore_autogenerated
package api
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"strings"
"testing"
"github.com/lukaszraczylo/go-telegram/client"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
// genTestMockDoer is a testify-mock HTTPDoer used by generated tests only.
type genTestMockDoer struct{ mock.Mock }
func (m *genTestMockDoer) Do(r *http.Request) (*http.Response, error) {
args := m.Called(r)
if v := args.Get(0); v != nil {
return v.(*http.Response), args.Error(1)
}
return nil, args.Error(1)
}
func genTestResp(status int, body string) *http.Response {
return &http.Response{
StatusCode: status,
Body: io.NopCloser(bytes.NewBufferString(body)),
Header: http.Header{"Content-Type": []string{"application/json"}},
}
}
func Test_GetUpdates_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getUpdates")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUpdatesParams{}
_, err := GetUpdates(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetUpdates_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUpdatesParams{}
_, err := GetUpdates(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetUpdates_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUpdatesParams{}
_, err := GetUpdates(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetUpdates_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUpdatesParams{}
_, err := GetUpdates(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetUpdates_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUpdatesParams{}
_, err := GetUpdates(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetUpdates_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUpdates_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetUpdates(context.Background(), bot, &GetUpdatesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetUpdates_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUpdates_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUpdatesParams{}
_, err := GetUpdates(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetUpdates_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUpdates_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUpdatesParams{}
_, err := GetUpdates(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetWebhook_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setWebhook")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetWebhookParams{
URL: "test_value",
}
_, err := SetWebhook(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetWebhook_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetWebhookParams{
URL: "test_value",
}
_, err := SetWebhook(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetWebhook_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetWebhookParams{
URL: "test_value",
}
_, err := SetWebhook(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetWebhook_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetWebhookParams{
URL: "test_value",
}
_, err := SetWebhook(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetWebhook_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetWebhookParams{
URL: "test_value",
}
_, err := SetWebhook(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetWebhook_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetWebhook_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetWebhook(context.Background(), bot, &SetWebhookParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetWebhook_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetWebhook_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetWebhookParams{
URL: "test_value",
}
_, err := SetWebhook(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetWebhook_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetWebhook_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetWebhookParams{
URL: "test_value",
}
_, err := SetWebhook(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteWebhook_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteWebhook")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteWebhookParams{}
_, err := DeleteWebhook(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteWebhook_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteWebhookParams{}
_, err := DeleteWebhook(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteWebhook_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteWebhookParams{}
_, err := DeleteWebhook(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteWebhook_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteWebhookParams{}
_, err := DeleteWebhook(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteWebhook_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteWebhookParams{}
_, err := DeleteWebhook(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteWebhook_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteWebhook_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteWebhook(context.Background(), bot, &DeleteWebhookParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteWebhook_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteWebhook_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteWebhookParams{}
_, err := DeleteWebhook(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteWebhook_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteWebhook_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteWebhookParams{}
_, err := DeleteWebhook(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetWebhookInfo_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getWebhookInfo")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
require.NoError(t, err)
}
func Test_GetWebhookInfo_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetWebhookInfo_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetWebhookInfo_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetWebhookInfo_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetWebhookInfo(ctx, bot, &GetWebhookInfoParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetWebhookInfo_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetWebhookInfo_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetWebhookInfo_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetWebhookInfo_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetWebhookInfo_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetWebhookInfo_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetWebhookInfo(context.Background(), bot, &GetWebhookInfoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetMe_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getMe")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMe(context.Background(), bot, &GetMeParams{})
require.NoError(t, err)
}
func Test_GetMe_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMe(context.Background(), bot, &GetMeParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetMe_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMe(context.Background(), bot, &GetMeParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetMe_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMe(context.Background(), bot, &GetMeParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetMe_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMe(ctx, bot, &GetMeParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetMe_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMe_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetMe(context.Background(), bot, &GetMeParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetMe_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMe_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMe(context.Background(), bot, &GetMeParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetMe_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMe_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMe(context.Background(), bot, &GetMeParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_LogOut_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/logOut")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := LogOut(context.Background(), bot, &LogOutParams{})
require.NoError(t, err)
}
func Test_LogOut_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := LogOut(context.Background(), bot, &LogOutParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_LogOut_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := LogOut(context.Background(), bot, &LogOutParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_LogOut_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := LogOut(context.Background(), bot, &LogOutParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_LogOut_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := LogOut(ctx, bot, &LogOutParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_LogOut_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_LogOut_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := LogOut(context.Background(), bot, &LogOutParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_LogOut_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_LogOut_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := LogOut(context.Background(), bot, &LogOutParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_LogOut_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_LogOut_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := LogOut(context.Background(), bot, &LogOutParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_Close_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/close")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := Close(context.Background(), bot, &CloseParams{})
require.NoError(t, err)
}
func Test_Close_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := Close(context.Background(), bot, &CloseParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_Close_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := Close(context.Background(), bot, &CloseParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_Close_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := Close(context.Background(), bot, &CloseParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_Close_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := Close(ctx, bot, &CloseParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_Close_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_Close_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := Close(context.Background(), bot, &CloseParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_Close_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_Close_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := Close(context.Background(), bot, &CloseParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_Close_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_Close_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := Close(context.Background(), bot, &CloseParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendMessage")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageParams{
ChatID: ChatIDFromInt(123),
Text: "test_value",
}
_, err := SendMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageParams{
ChatID: ChatIDFromInt(123),
Text: "test_value",
}
_, err := SendMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageParams{
ChatID: ChatIDFromInt(123),
Text: "test_value",
}
_, err := SendMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageParams{
ChatID: ChatIDFromInt(123),
Text: "test_value",
}
_, err := SendMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageParams{
ChatID: ChatIDFromInt(123),
Text: "test_value",
}
_, err := SendMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendMessage(context.Background(), bot, &SendMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageParams{
ChatID: ChatIDFromInt(123),
Text: "test_value",
}
_, err := SendMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageParams{
ChatID: ChatIDFromInt(123),
Text: "test_value",
}
_, err := SendMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ForwardMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/forwardMessage")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := ForwardMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ForwardMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := ForwardMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ForwardMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := ForwardMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ForwardMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := ForwardMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ForwardMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := ForwardMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ForwardMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ForwardMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ForwardMessage(context.Background(), bot, &ForwardMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ForwardMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ForwardMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := ForwardMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ForwardMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ForwardMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := ForwardMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ForwardMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/forwardMessages")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := ForwardMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ForwardMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := ForwardMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ForwardMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := ForwardMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ForwardMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := ForwardMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ForwardMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := ForwardMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ForwardMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ForwardMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ForwardMessages(context.Background(), bot, &ForwardMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ForwardMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ForwardMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := ForwardMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ForwardMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ForwardMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ForwardMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := ForwardMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CopyMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/copyMessage")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := CopyMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CopyMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := CopyMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CopyMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := CopyMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CopyMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := CopyMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CopyMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := CopyMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CopyMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CopyMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CopyMessage(context.Background(), bot, &CopyMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CopyMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CopyMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := CopyMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CopyMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CopyMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessageParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := CopyMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CopyMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/copyMessages")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := CopyMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CopyMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := CopyMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CopyMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := CopyMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CopyMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := CopyMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CopyMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := CopyMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CopyMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CopyMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CopyMessages(context.Background(), bot, &CopyMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CopyMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CopyMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := CopyMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CopyMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CopyMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CopyMessagesParams{
ChatID: ChatIDFromInt(123),
FromChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := CopyMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendPhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendPhoto")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendPhoto(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendPhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendPhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendPhoto(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendPhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendPhoto(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendPhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendPhoto(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendPhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendPhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendPhoto(context.Background(), bot, &SendPhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendPhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendPhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendPhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendPhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendLivePhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendLivePhoto")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLivePhotoParams{
ChatID: ChatIDFromInt(123),
LivePhoto: &InputFile{PathOrID: "file_id_test"},
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendLivePhoto(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendLivePhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLivePhotoParams{
ChatID: ChatIDFromInt(123),
LivePhoto: &InputFile{PathOrID: "file_id_test"},
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendLivePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendLivePhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLivePhotoParams{
ChatID: ChatIDFromInt(123),
LivePhoto: &InputFile{PathOrID: "file_id_test"},
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendLivePhoto(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendLivePhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLivePhotoParams{
ChatID: ChatIDFromInt(123),
LivePhoto: &InputFile{PathOrID: "file_id_test"},
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendLivePhoto(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendLivePhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLivePhotoParams{
ChatID: ChatIDFromInt(123),
LivePhoto: &InputFile{PathOrID: "file_id_test"},
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendLivePhoto(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendLivePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendLivePhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendLivePhoto(context.Background(), bot, &SendLivePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendLivePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendLivePhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLivePhotoParams{
ChatID: ChatIDFromInt(123),
LivePhoto: &InputFile{PathOrID: "file_id_test"},
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendLivePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendLivePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendLivePhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLivePhotoParams{
ChatID: ChatIDFromInt(123),
LivePhoto: &InputFile{PathOrID: "file_id_test"},
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendLivePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendAudio_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendAudio")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAudioParams{
ChatID: ChatIDFromInt(123),
Audio: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAudio(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendAudio_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAudioParams{
ChatID: ChatIDFromInt(123),
Audio: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAudio(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendAudio_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAudioParams{
ChatID: ChatIDFromInt(123),
Audio: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAudio(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendAudio_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAudioParams{
ChatID: ChatIDFromInt(123),
Audio: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAudio(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendAudio_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAudioParams{
ChatID: ChatIDFromInt(123),
Audio: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAudio(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendAudio_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendAudio_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendAudio(context.Background(), bot, &SendAudioParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendAudio_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendAudio_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAudioParams{
ChatID: ChatIDFromInt(123),
Audio: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAudio(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendAudio_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendAudio_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAudioParams{
ChatID: ChatIDFromInt(123),
Audio: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAudio(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendDocument_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendDocument")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDocumentParams{
ChatID: ChatIDFromInt(123),
Document: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendDocument(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendDocument_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDocumentParams{
ChatID: ChatIDFromInt(123),
Document: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendDocument(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendDocument_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDocumentParams{
ChatID: ChatIDFromInt(123),
Document: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendDocument(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendDocument_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDocumentParams{
ChatID: ChatIDFromInt(123),
Document: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendDocument(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendDocument_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDocumentParams{
ChatID: ChatIDFromInt(123),
Document: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendDocument(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendDocument_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendDocument_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendDocument(context.Background(), bot, &SendDocumentParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendDocument_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendDocument_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDocumentParams{
ChatID: ChatIDFromInt(123),
Document: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendDocument(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendDocument_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendDocument_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDocumentParams{
ChatID: ChatIDFromInt(123),
Document: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendDocument(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendVideo_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendVideo")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoParams{
ChatID: ChatIDFromInt(123),
Video: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideo(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendVideo_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoParams{
ChatID: ChatIDFromInt(123),
Video: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideo(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendVideo_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoParams{
ChatID: ChatIDFromInt(123),
Video: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideo(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendVideo_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoParams{
ChatID: ChatIDFromInt(123),
Video: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideo(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendVideo_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoParams{
ChatID: ChatIDFromInt(123),
Video: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideo(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendVideo_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVideo_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendVideo(context.Background(), bot, &SendVideoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendVideo_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVideo_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoParams{
ChatID: ChatIDFromInt(123),
Video: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideo(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendVideo_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVideo_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoParams{
ChatID: ChatIDFromInt(123),
Video: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideo(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendAnimation_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendAnimation")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAnimationParams{
ChatID: ChatIDFromInt(123),
Animation: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAnimation(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendAnimation_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAnimationParams{
ChatID: ChatIDFromInt(123),
Animation: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAnimation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendAnimation_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAnimationParams{
ChatID: ChatIDFromInt(123),
Animation: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAnimation(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendAnimation_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAnimationParams{
ChatID: ChatIDFromInt(123),
Animation: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAnimation(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendAnimation_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAnimationParams{
ChatID: ChatIDFromInt(123),
Animation: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAnimation(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendAnimation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendAnimation_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendAnimation(context.Background(), bot, &SendAnimationParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendAnimation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendAnimation_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAnimationParams{
ChatID: ChatIDFromInt(123),
Animation: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAnimation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendAnimation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendAnimation_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendAnimationParams{
ChatID: ChatIDFromInt(123),
Animation: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendAnimation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendVoice_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendVoice")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVoiceParams{
ChatID: ChatIDFromInt(123),
Voice: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVoice(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendVoice_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVoiceParams{
ChatID: ChatIDFromInt(123),
Voice: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVoice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendVoice_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVoiceParams{
ChatID: ChatIDFromInt(123),
Voice: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVoice(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendVoice_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVoiceParams{
ChatID: ChatIDFromInt(123),
Voice: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVoice(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendVoice_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVoiceParams{
ChatID: ChatIDFromInt(123),
Voice: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVoice(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendVoice_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVoice_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendVoice(context.Background(), bot, &SendVoiceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendVoice_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVoice_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVoiceParams{
ChatID: ChatIDFromInt(123),
Voice: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVoice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendVoice_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVoice_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVoiceParams{
ChatID: ChatIDFromInt(123),
Voice: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVoice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendVideoNote_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendVideoNote")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoNoteParams{
ChatID: ChatIDFromInt(123),
VideoNote: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideoNote(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendVideoNote_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoNoteParams{
ChatID: ChatIDFromInt(123),
VideoNote: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideoNote(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendVideoNote_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoNoteParams{
ChatID: ChatIDFromInt(123),
VideoNote: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideoNote(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendVideoNote_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoNoteParams{
ChatID: ChatIDFromInt(123),
VideoNote: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideoNote(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendVideoNote_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoNoteParams{
ChatID: ChatIDFromInt(123),
VideoNote: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideoNote(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendVideoNote_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVideoNote_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendVideoNote(context.Background(), bot, &SendVideoNoteParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendVideoNote_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVideoNote_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoNoteParams{
ChatID: ChatIDFromInt(123),
VideoNote: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideoNote(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendVideoNote_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVideoNote_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVideoNoteParams{
ChatID: ChatIDFromInt(123),
VideoNote: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendVideoNote(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendPaidMedia_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendPaidMedia")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPaidMediaParams{
ChatID: ChatIDFromInt(123),
StarCount: 42,
Media: nil,
}
_, err := SendPaidMedia(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendPaidMedia_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPaidMediaParams{
ChatID: ChatIDFromInt(123),
StarCount: 42,
Media: nil,
}
_, err := SendPaidMedia(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendPaidMedia_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPaidMediaParams{
ChatID: ChatIDFromInt(123),
StarCount: 42,
Media: nil,
}
_, err := SendPaidMedia(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendPaidMedia_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPaidMediaParams{
ChatID: ChatIDFromInt(123),
StarCount: 42,
Media: nil,
}
_, err := SendPaidMedia(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendPaidMedia_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPaidMediaParams{
ChatID: ChatIDFromInt(123),
StarCount: 42,
Media: nil,
}
_, err := SendPaidMedia(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendPaidMedia_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendPaidMedia_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendPaidMedia(context.Background(), bot, &SendPaidMediaParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendPaidMedia_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendPaidMedia_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPaidMediaParams{
ChatID: ChatIDFromInt(123),
StarCount: 42,
Media: nil,
}
_, err := SendPaidMedia(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendPaidMedia_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendPaidMedia_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPaidMediaParams{
ChatID: ChatIDFromInt(123),
StarCount: 42,
Media: nil,
}
_, err := SendPaidMedia(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendMediaGroup_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendMediaGroup")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMediaGroupParams{
ChatID: ChatIDFromInt(123),
Media: nil,
}
_, err := SendMediaGroup(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendMediaGroup_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMediaGroupParams{
ChatID: ChatIDFromInt(123),
Media: nil,
}
_, err := SendMediaGroup(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendMediaGroup_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMediaGroupParams{
ChatID: ChatIDFromInt(123),
Media: nil,
}
_, err := SendMediaGroup(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendMediaGroup_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMediaGroupParams{
ChatID: ChatIDFromInt(123),
Media: nil,
}
_, err := SendMediaGroup(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendMediaGroup_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMediaGroupParams{
ChatID: ChatIDFromInt(123),
Media: nil,
}
_, err := SendMediaGroup(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendMediaGroup_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendMediaGroup_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendMediaGroup(context.Background(), bot, &SendMediaGroupParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendMediaGroup_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendMediaGroup_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMediaGroupParams{
ChatID: ChatIDFromInt(123),
Media: nil,
}
_, err := SendMediaGroup(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendMediaGroup_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendMediaGroup_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMediaGroupParams{
ChatID: ChatIDFromInt(123),
Media: nil,
}
_, err := SendMediaGroup(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendLocation_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendLocation")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLocationParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
}
_, err := SendLocation(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendLocation_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLocationParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
}
_, err := SendLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendLocation_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLocationParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
}
_, err := SendLocation(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendLocation_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLocationParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
}
_, err := SendLocation(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendLocation_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLocationParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
}
_, err := SendLocation(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendLocation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendLocation_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendLocation(context.Background(), bot, &SendLocationParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendLocation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendLocation_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLocationParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
}
_, err := SendLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendLocation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendLocation_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendLocationParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
}
_, err := SendLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendVenue_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendVenue")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVenueParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
Title: "test_value",
Address: "test_value",
}
_, err := SendVenue(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendVenue_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVenueParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
Title: "test_value",
Address: "test_value",
}
_, err := SendVenue(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendVenue_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVenueParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
Title: "test_value",
Address: "test_value",
}
_, err := SendVenue(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendVenue_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVenueParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
Title: "test_value",
Address: "test_value",
}
_, err := SendVenue(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendVenue_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVenueParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
Title: "test_value",
Address: "test_value",
}
_, err := SendVenue(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendVenue_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendVenue_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendVenue(context.Background(), bot, &SendVenueParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendVenue_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendVenue_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVenueParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
Title: "test_value",
Address: "test_value",
}
_, err := SendVenue(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendVenue_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendVenue_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendVenueParams{
ChatID: ChatIDFromInt(123),
Latitude: 1.0,
Longitude: 1.0,
Title: "test_value",
Address: "test_value",
}
_, err := SendVenue(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendContact_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendContact")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendContactParams{
ChatID: ChatIDFromInt(123),
PhoneNumber: "test_value",
FirstName: "test_value",
}
_, err := SendContact(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendContact_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendContactParams{
ChatID: ChatIDFromInt(123),
PhoneNumber: "test_value",
FirstName: "test_value",
}
_, err := SendContact(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendContact_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendContactParams{
ChatID: ChatIDFromInt(123),
PhoneNumber: "test_value",
FirstName: "test_value",
}
_, err := SendContact(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendContact_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendContactParams{
ChatID: ChatIDFromInt(123),
PhoneNumber: "test_value",
FirstName: "test_value",
}
_, err := SendContact(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendContact_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendContactParams{
ChatID: ChatIDFromInt(123),
PhoneNumber: "test_value",
FirstName: "test_value",
}
_, err := SendContact(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendContact_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendContact_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendContact(context.Background(), bot, &SendContactParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendContact_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendContact_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendContactParams{
ChatID: ChatIDFromInt(123),
PhoneNumber: "test_value",
FirstName: "test_value",
}
_, err := SendContact(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendContact_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendContact_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendContactParams{
ChatID: ChatIDFromInt(123),
PhoneNumber: "test_value",
FirstName: "test_value",
}
_, err := SendContact(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendPoll_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendPoll")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPollParams{
ChatID: ChatIDFromInt(123),
Question: "test_value",
Options: nil,
}
_, err := SendPoll(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendPoll_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPollParams{
ChatID: ChatIDFromInt(123),
Question: "test_value",
Options: nil,
}
_, err := SendPoll(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendPoll_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPollParams{
ChatID: ChatIDFromInt(123),
Question: "test_value",
Options: nil,
}
_, err := SendPoll(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendPoll_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPollParams{
ChatID: ChatIDFromInt(123),
Question: "test_value",
Options: nil,
}
_, err := SendPoll(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendPoll_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPollParams{
ChatID: ChatIDFromInt(123),
Question: "test_value",
Options: nil,
}
_, err := SendPoll(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendPoll_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendPoll_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendPoll(context.Background(), bot, &SendPollParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendPoll_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendPoll_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPollParams{
ChatID: ChatIDFromInt(123),
Question: "test_value",
Options: nil,
}
_, err := SendPoll(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendPoll_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendPoll_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendPollParams{
ChatID: ChatIDFromInt(123),
Question: "test_value",
Options: nil,
}
_, err := SendPoll(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendChecklist_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendChecklist")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
Checklist: InputChecklist{},
}
_, err := SendChecklist(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendChecklist_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
Checklist: InputChecklist{},
}
_, err := SendChecklist(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendChecklist_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
Checklist: InputChecklist{},
}
_, err := SendChecklist(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendChecklist_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
Checklist: InputChecklist{},
}
_, err := SendChecklist(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendChecklist_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
Checklist: InputChecklist{},
}
_, err := SendChecklist(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendChecklist_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendChecklist_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendChecklist(context.Background(), bot, &SendChecklistParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendChecklist_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendChecklist_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
Checklist: InputChecklist{},
}
_, err := SendChecklist(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendChecklist_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendChecklist_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
Checklist: InputChecklist{},
}
_, err := SendChecklist(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendDice_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendDice")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDiceParams{
ChatID: ChatIDFromInt(123),
}
_, err := SendDice(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendDice_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDiceParams{
ChatID: ChatIDFromInt(123),
}
_, err := SendDice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendDice_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDiceParams{
ChatID: ChatIDFromInt(123),
}
_, err := SendDice(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendDice_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDiceParams{
ChatID: ChatIDFromInt(123),
}
_, err := SendDice(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendDice_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDiceParams{
ChatID: ChatIDFromInt(123),
}
_, err := SendDice(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendDice_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendDice_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendDice(context.Background(), bot, &SendDiceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendDice_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendDice_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDiceParams{
ChatID: ChatIDFromInt(123),
}
_, err := SendDice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendDice_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendDice_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendDiceParams{
ChatID: ChatIDFromInt(123),
}
_, err := SendDice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendMessageDraft_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendMessageDraft")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageDraftParams{
ChatID: 42,
DraftID: 42,
}
_, err := SendMessageDraft(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendMessageDraft_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageDraftParams{
ChatID: 42,
DraftID: 42,
}
_, err := SendMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendMessageDraft_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageDraftParams{
ChatID: 42,
DraftID: 42,
}
_, err := SendMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendMessageDraft_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageDraftParams{
ChatID: 42,
DraftID: 42,
}
_, err := SendMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendMessageDraft_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageDraftParams{
ChatID: 42,
DraftID: 42,
}
_, err := SendMessageDraft(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendMessageDraft_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendMessageDraft_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendMessageDraft(context.Background(), bot, &SendMessageDraftParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendMessageDraft_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendMessageDraft_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageDraftParams{
ChatID: 42,
DraftID: 42,
}
_, err := SendMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendMessageDraft_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendMessageDraft_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendMessageDraftParams{
ChatID: 42,
DraftID: 42,
}
_, err := SendMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendChatAction_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendChatAction")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatActionParams{
ChatID: ChatIDFromInt(123),
Action: "test_value",
}
_, err := SendChatAction(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendChatAction_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatActionParams{
ChatID: ChatIDFromInt(123),
Action: "test_value",
}
_, err := SendChatAction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendChatAction_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatActionParams{
ChatID: ChatIDFromInt(123),
Action: "test_value",
}
_, err := SendChatAction(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendChatAction_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatActionParams{
ChatID: ChatIDFromInt(123),
Action: "test_value",
}
_, err := SendChatAction(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendChatAction_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatActionParams{
ChatID: ChatIDFromInt(123),
Action: "test_value",
}
_, err := SendChatAction(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendChatAction_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendChatAction_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendChatAction(context.Background(), bot, &SendChatActionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendChatAction_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendChatAction_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatActionParams{
ChatID: ChatIDFromInt(123),
Action: "test_value",
}
_, err := SendChatAction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendChatAction_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendChatAction_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatActionParams{
ChatID: ChatIDFromInt(123),
Action: "test_value",
}
_, err := SendChatAction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetMessageReaction_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setMessageReaction")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := SetMessageReaction(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetMessageReaction_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := SetMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetMessageReaction_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := SetMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetMessageReaction_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := SetMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetMessageReaction_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := SetMessageReaction(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetMessageReaction_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMessageReaction_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetMessageReaction(context.Background(), bot, &SetMessageReactionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetMessageReaction_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMessageReaction_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := SetMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetMessageReaction_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMessageReaction_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := SetMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetUserProfilePhotos_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getUserProfilePhotos")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfilePhotosParams{
UserID: 42,
}
_, err := GetUserProfilePhotos(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetUserProfilePhotos_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfilePhotosParams{
UserID: 42,
}
_, err := GetUserProfilePhotos(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetUserProfilePhotos_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfilePhotosParams{
UserID: 42,
}
_, err := GetUserProfilePhotos(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetUserProfilePhotos_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfilePhotosParams{
UserID: 42,
}
_, err := GetUserProfilePhotos(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetUserProfilePhotos_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfilePhotosParams{
UserID: 42,
}
_, err := GetUserProfilePhotos(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetUserProfilePhotos_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserProfilePhotos_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetUserProfilePhotos(context.Background(), bot, &GetUserProfilePhotosParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetUserProfilePhotos_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserProfilePhotos_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfilePhotosParams{
UserID: 42,
}
_, err := GetUserProfilePhotos(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetUserProfilePhotos_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserProfilePhotos_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfilePhotosParams{
UserID: 42,
}
_, err := GetUserProfilePhotos(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetUserProfileAudios_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getUserProfileAudios")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfileAudiosParams{
UserID: 42,
}
_, err := GetUserProfileAudios(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetUserProfileAudios_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfileAudiosParams{
UserID: 42,
}
_, err := GetUserProfileAudios(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetUserProfileAudios_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfileAudiosParams{
UserID: 42,
}
_, err := GetUserProfileAudios(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetUserProfileAudios_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfileAudiosParams{
UserID: 42,
}
_, err := GetUserProfileAudios(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetUserProfileAudios_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfileAudiosParams{
UserID: 42,
}
_, err := GetUserProfileAudios(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetUserProfileAudios_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserProfileAudios_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetUserProfileAudios(context.Background(), bot, &GetUserProfileAudiosParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetUserProfileAudios_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserProfileAudios_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfileAudiosParams{
UserID: 42,
}
_, err := GetUserProfileAudios(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetUserProfileAudios_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserProfileAudios_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserProfileAudiosParams{
UserID: 42,
}
_, err := GetUserProfileAudios(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetUserEmojiStatus_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setUserEmojiStatus")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetUserEmojiStatusParams{
UserID: 42,
}
_, err := SetUserEmojiStatus(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetUserEmojiStatus_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetUserEmojiStatusParams{
UserID: 42,
}
_, err := SetUserEmojiStatus(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetUserEmojiStatus_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetUserEmojiStatusParams{
UserID: 42,
}
_, err := SetUserEmojiStatus(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetUserEmojiStatus_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetUserEmojiStatusParams{
UserID: 42,
}
_, err := SetUserEmojiStatus(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetUserEmojiStatus_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetUserEmojiStatusParams{
UserID: 42,
}
_, err := SetUserEmojiStatus(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetUserEmojiStatus_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetUserEmojiStatus_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetUserEmojiStatus(context.Background(), bot, &SetUserEmojiStatusParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetUserEmojiStatus_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetUserEmojiStatus_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetUserEmojiStatusParams{
UserID: 42,
}
_, err := SetUserEmojiStatus(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetUserEmojiStatus_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetUserEmojiStatus_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetUserEmojiStatusParams{
UserID: 42,
}
_, err := SetUserEmojiStatus(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetFile_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getFile")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetFileParams{
FileID: "test_value",
}
_, err := GetFile(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetFile_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetFileParams{
FileID: "test_value",
}
_, err := GetFile(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetFile_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetFileParams{
FileID: "test_value",
}
_, err := GetFile(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetFile_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetFileParams{
FileID: "test_value",
}
_, err := GetFile(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetFile_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetFileParams{
FileID: "test_value",
}
_, err := GetFile(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetFile_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetFile_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetFile(context.Background(), bot, &GetFileParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetFile_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetFile_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetFileParams{
FileID: "test_value",
}
_, err := GetFile(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetFile_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetFile_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetFileParams{
FileID: "test_value",
}
_, err := GetFile(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_BanChatMember_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/banChatMember")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := BanChatMember(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_BanChatMember_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := BanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_BanChatMember_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := BanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_BanChatMember_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := BanChatMember(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_BanChatMember_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := BanChatMember(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_BanChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_BanChatMember_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := BanChatMember(context.Background(), bot, &BanChatMemberParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_BanChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_BanChatMember_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := BanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_BanChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_BanChatMember_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := BanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UnbanChatMember_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/unbanChatMember")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := UnbanChatMember(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UnbanChatMember_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := UnbanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UnbanChatMember_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := UnbanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UnbanChatMember_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := UnbanChatMember(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UnbanChatMember_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := UnbanChatMember(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UnbanChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnbanChatMember_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UnbanChatMember(context.Background(), bot, &UnbanChatMemberParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UnbanChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnbanChatMember_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := UnbanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UnbanChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnbanChatMember_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := UnbanChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RestrictChatMember_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/restrictChatMember")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RestrictChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
Permissions: ChatPermissions{},
}
_, err := RestrictChatMember(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_RestrictChatMember_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RestrictChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
Permissions: ChatPermissions{},
}
_, err := RestrictChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RestrictChatMember_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RestrictChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
Permissions: ChatPermissions{},
}
_, err := RestrictChatMember(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RestrictChatMember_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RestrictChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
Permissions: ChatPermissions{},
}
_, err := RestrictChatMember(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RestrictChatMember_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RestrictChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
Permissions: ChatPermissions{},
}
_, err := RestrictChatMember(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RestrictChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RestrictChatMember_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RestrictChatMember(context.Background(), bot, &RestrictChatMemberParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RestrictChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RestrictChatMember_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RestrictChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
Permissions: ChatPermissions{},
}
_, err := RestrictChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RestrictChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RestrictChatMember_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RestrictChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
Permissions: ChatPermissions{},
}
_, err := RestrictChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_PromoteChatMember_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/promoteChatMember")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PromoteChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := PromoteChatMember(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_PromoteChatMember_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PromoteChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := PromoteChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_PromoteChatMember_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PromoteChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := PromoteChatMember(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_PromoteChatMember_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PromoteChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := PromoteChatMember(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_PromoteChatMember_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PromoteChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := PromoteChatMember(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_PromoteChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_PromoteChatMember_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := PromoteChatMember(context.Background(), bot, &PromoteChatMemberParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_PromoteChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_PromoteChatMember_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PromoteChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := PromoteChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_PromoteChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_PromoteChatMember_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PromoteChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := PromoteChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatAdministratorCustomTitle_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatAdministratorCustomTitle")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatAdministratorCustomTitleParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
CustomTitle: "test_value",
}
_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatAdministratorCustomTitle_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatAdministratorCustomTitleParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
CustomTitle: "test_value",
}
_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatAdministratorCustomTitle_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatAdministratorCustomTitleParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
CustomTitle: "test_value",
}
_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatAdministratorCustomTitle_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatAdministratorCustomTitleParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
CustomTitle: "test_value",
}
_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatAdministratorCustomTitle_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatAdministratorCustomTitleParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
CustomTitle: "test_value",
}
_, err := SetChatAdministratorCustomTitle(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatAdministratorCustomTitle_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatAdministratorCustomTitle_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatAdministratorCustomTitle(context.Background(), bot, &SetChatAdministratorCustomTitleParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatAdministratorCustomTitle_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatAdministratorCustomTitle_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatAdministratorCustomTitleParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
CustomTitle: "test_value",
}
_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatAdministratorCustomTitle_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatAdministratorCustomTitle_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatAdministratorCustomTitleParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
CustomTitle: "test_value",
}
_, err := SetChatAdministratorCustomTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatMemberTag_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatMemberTag")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMemberTagParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := SetChatMemberTag(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatMemberTag_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMemberTagParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := SetChatMemberTag(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatMemberTag_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMemberTagParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := SetChatMemberTag(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatMemberTag_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMemberTagParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := SetChatMemberTag(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatMemberTag_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMemberTagParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := SetChatMemberTag(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatMemberTag_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatMemberTag_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatMemberTag(context.Background(), bot, &SetChatMemberTagParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatMemberTag_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatMemberTag_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMemberTagParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := SetChatMemberTag(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatMemberTag_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatMemberTag_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMemberTagParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := SetChatMemberTag(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_BanChatSenderChat_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/banChatSenderChat")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := BanChatSenderChat(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_BanChatSenderChat_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := BanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_BanChatSenderChat_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := BanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_BanChatSenderChat_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := BanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_BanChatSenderChat_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := BanChatSenderChat(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_BanChatSenderChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_BanChatSenderChat_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := BanChatSenderChat(context.Background(), bot, &BanChatSenderChatParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_BanChatSenderChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_BanChatSenderChat_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := BanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_BanChatSenderChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_BanChatSenderChat_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &BanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := BanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UnbanChatSenderChat_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/unbanChatSenderChat")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := UnbanChatSenderChat(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UnbanChatSenderChat_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := UnbanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UnbanChatSenderChat_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := UnbanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UnbanChatSenderChat_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := UnbanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UnbanChatSenderChat_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := UnbanChatSenderChat(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UnbanChatSenderChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnbanChatSenderChat_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UnbanChatSenderChat(context.Background(), bot, &UnbanChatSenderChatParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UnbanChatSenderChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnbanChatSenderChat_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := UnbanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UnbanChatSenderChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnbanChatSenderChat_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnbanChatSenderChatParams{
ChatID: ChatIDFromInt(123),
SenderChatID: 42,
}
_, err := UnbanChatSenderChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatPermissions_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatPermissions")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPermissionsParams{
ChatID: ChatIDFromInt(123),
Permissions: ChatPermissions{},
}
_, err := SetChatPermissions(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatPermissions_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPermissionsParams{
ChatID: ChatIDFromInt(123),
Permissions: ChatPermissions{},
}
_, err := SetChatPermissions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatPermissions_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPermissionsParams{
ChatID: ChatIDFromInt(123),
Permissions: ChatPermissions{},
}
_, err := SetChatPermissions(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatPermissions_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPermissionsParams{
ChatID: ChatIDFromInt(123),
Permissions: ChatPermissions{},
}
_, err := SetChatPermissions(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatPermissions_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPermissionsParams{
ChatID: ChatIDFromInt(123),
Permissions: ChatPermissions{},
}
_, err := SetChatPermissions(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatPermissions_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatPermissions_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatPermissions(context.Background(), bot, &SetChatPermissionsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatPermissions_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatPermissions_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPermissionsParams{
ChatID: ChatIDFromInt(123),
Permissions: ChatPermissions{},
}
_, err := SetChatPermissions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatPermissions_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatPermissions_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPermissionsParams{
ChatID: ChatIDFromInt(123),
Permissions: ChatPermissions{},
}
_, err := SetChatPermissions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ExportChatInviteLink_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/exportChatInviteLink")
})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ExportChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := ExportChatInviteLink(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ExportChatInviteLink_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ExportChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := ExportChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ExportChatInviteLink_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ExportChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := ExportChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ExportChatInviteLink_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ExportChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := ExportChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ExportChatInviteLink_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ExportChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := ExportChatInviteLink(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ExportChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ExportChatInviteLink_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ExportChatInviteLink(context.Background(), bot, &ExportChatInviteLinkParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ExportChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ExportChatInviteLink_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ExportChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := ExportChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ExportChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ExportChatInviteLink_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ExportChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := ExportChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CreateChatInviteLink_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/createChatInviteLink")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := CreateChatInviteLink(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CreateChatInviteLink_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := CreateChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CreateChatInviteLink_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := CreateChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CreateChatInviteLink_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := CreateChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CreateChatInviteLink_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := CreateChatInviteLink(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CreateChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateChatInviteLink_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CreateChatInviteLink(context.Background(), bot, &CreateChatInviteLinkParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CreateChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateChatInviteLink_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := CreateChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CreateChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateChatInviteLink_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
}
_, err := CreateChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditChatInviteLink_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editChatInviteLink")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatInviteLink(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditChatInviteLink_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditChatInviteLink_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditChatInviteLink_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditChatInviteLink_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatInviteLink(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditChatInviteLink_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditChatInviteLink(context.Background(), bot, &EditChatInviteLinkParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditChatInviteLink_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditChatInviteLink_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CreateChatSubscriptionInviteLink_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/createChatSubscriptionInviteLink")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
SubscriptionPeriod: 42,
SubscriptionPrice: 42,
}
_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CreateChatSubscriptionInviteLink_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
SubscriptionPeriod: 42,
SubscriptionPrice: 42,
}
_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CreateChatSubscriptionInviteLink_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
SubscriptionPeriod: 42,
SubscriptionPrice: 42,
}
_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CreateChatSubscriptionInviteLink_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
SubscriptionPeriod: 42,
SubscriptionPrice: 42,
}
_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CreateChatSubscriptionInviteLink_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
SubscriptionPeriod: 42,
SubscriptionPrice: 42,
}
_, err := CreateChatSubscriptionInviteLink(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CreateChatSubscriptionInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateChatSubscriptionInviteLink_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, &CreateChatSubscriptionInviteLinkParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CreateChatSubscriptionInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateChatSubscriptionInviteLink_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
SubscriptionPeriod: 42,
SubscriptionPrice: 42,
}
_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CreateChatSubscriptionInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateChatSubscriptionInviteLink_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
SubscriptionPeriod: 42,
SubscriptionPrice: 42,
}
_, err := CreateChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditChatSubscriptionInviteLink_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editChatSubscriptionInviteLink")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditChatSubscriptionInviteLink_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditChatSubscriptionInviteLink_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditChatSubscriptionInviteLink_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditChatSubscriptionInviteLink_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatSubscriptionInviteLink(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditChatSubscriptionInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditChatSubscriptionInviteLink_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditChatSubscriptionInviteLink(context.Background(), bot, &EditChatSubscriptionInviteLinkParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditChatSubscriptionInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditChatSubscriptionInviteLink_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditChatSubscriptionInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditChatSubscriptionInviteLink_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditChatSubscriptionInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := EditChatSubscriptionInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RevokeChatInviteLink_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/revokeChatInviteLink")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RevokeChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := RevokeChatInviteLink(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_RevokeChatInviteLink_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RevokeChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := RevokeChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RevokeChatInviteLink_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RevokeChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := RevokeChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RevokeChatInviteLink_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RevokeChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := RevokeChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RevokeChatInviteLink_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RevokeChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := RevokeChatInviteLink(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RevokeChatInviteLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RevokeChatInviteLink_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RevokeChatInviteLink(context.Background(), bot, &RevokeChatInviteLinkParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RevokeChatInviteLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RevokeChatInviteLink_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RevokeChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := RevokeChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RevokeChatInviteLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RevokeChatInviteLink_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RevokeChatInviteLinkParams{
ChatID: ChatIDFromInt(123),
InviteLink: "test_value",
}
_, err := RevokeChatInviteLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ApproveChatJoinRequest_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/approveChatJoinRequest")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := ApproveChatJoinRequest(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ApproveChatJoinRequest_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := ApproveChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ApproveChatJoinRequest_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := ApproveChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ApproveChatJoinRequest_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := ApproveChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ApproveChatJoinRequest_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := ApproveChatJoinRequest(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ApproveChatJoinRequest_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ApproveChatJoinRequest_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ApproveChatJoinRequest(context.Background(), bot, &ApproveChatJoinRequestParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ApproveChatJoinRequest_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ApproveChatJoinRequest_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := ApproveChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ApproveChatJoinRequest_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ApproveChatJoinRequest_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := ApproveChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeclineChatJoinRequest_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/declineChatJoinRequest")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := DeclineChatJoinRequest(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeclineChatJoinRequest_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := DeclineChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeclineChatJoinRequest_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := DeclineChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeclineChatJoinRequest_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := DeclineChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeclineChatJoinRequest_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := DeclineChatJoinRequest(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeclineChatJoinRequest_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeclineChatJoinRequest_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeclineChatJoinRequest(context.Background(), bot, &DeclineChatJoinRequestParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeclineChatJoinRequest_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeclineChatJoinRequest_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := DeclineChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeclineChatJoinRequest_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeclineChatJoinRequest_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineChatJoinRequestParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := DeclineChatJoinRequest(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AnswerChatJoinRequestQuery_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/answerChatJoinRequestQuery")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerChatJoinRequestQueryParams{
ChatJoinRequestQueryID: "test_value",
Result: ResultApprove,
}
_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AnswerChatJoinRequestQuery_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerChatJoinRequestQueryParams{
ChatJoinRequestQueryID: "test_value",
Result: ResultApprove,
}
_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AnswerChatJoinRequestQuery_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerChatJoinRequestQueryParams{
ChatJoinRequestQueryID: "test_value",
Result: ResultApprove,
}
_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AnswerChatJoinRequestQuery_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerChatJoinRequestQueryParams{
ChatJoinRequestQueryID: "test_value",
Result: ResultApprove,
}
_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AnswerChatJoinRequestQuery_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerChatJoinRequestQueryParams{
ChatJoinRequestQueryID: "test_value",
Result: ResultApprove,
}
_, err := AnswerChatJoinRequestQuery(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AnswerChatJoinRequestQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerChatJoinRequestQuery_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AnswerChatJoinRequestQuery(context.Background(), bot, &AnswerChatJoinRequestQueryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AnswerChatJoinRequestQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerChatJoinRequestQuery_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerChatJoinRequestQueryParams{
ChatJoinRequestQueryID: "test_value",
Result: ResultApprove,
}
_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AnswerChatJoinRequestQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerChatJoinRequestQuery_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerChatJoinRequestQueryParams{
ChatJoinRequestQueryID: "test_value",
Result: ResultApprove,
}
_, err := AnswerChatJoinRequestQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendChatJoinRequestWebApp_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendChatJoinRequestWebApp")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatJoinRequestWebAppParams{
ChatJoinRequestQueryID: "test_value",
WebAppURL: "test_value",
}
_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendChatJoinRequestWebApp_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatJoinRequestWebAppParams{
ChatJoinRequestQueryID: "test_value",
WebAppURL: "test_value",
}
_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendChatJoinRequestWebApp_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatJoinRequestWebAppParams{
ChatJoinRequestQueryID: "test_value",
WebAppURL: "test_value",
}
_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendChatJoinRequestWebApp_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatJoinRequestWebAppParams{
ChatJoinRequestQueryID: "test_value",
WebAppURL: "test_value",
}
_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendChatJoinRequestWebApp_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatJoinRequestWebAppParams{
ChatJoinRequestQueryID: "test_value",
WebAppURL: "test_value",
}
_, err := SendChatJoinRequestWebApp(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendChatJoinRequestWebApp_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendChatJoinRequestWebApp_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendChatJoinRequestWebApp(context.Background(), bot, &SendChatJoinRequestWebAppParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendChatJoinRequestWebApp_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendChatJoinRequestWebApp_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatJoinRequestWebAppParams{
ChatJoinRequestQueryID: "test_value",
WebAppURL: "test_value",
}
_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendChatJoinRequestWebApp_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendChatJoinRequestWebApp_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendChatJoinRequestWebAppParams{
ChatJoinRequestQueryID: "test_value",
WebAppURL: "test_value",
}
_, err := SendChatJoinRequestWebApp(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatPhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatPhoto")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SetChatPhoto(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatPhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SetChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatPhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SetChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatPhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SetChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatPhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SetChatPhoto(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatPhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatPhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatPhoto(context.Background(), bot, &SetChatPhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatPhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatPhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SetChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatPhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatPhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatPhotoParams{
ChatID: ChatIDFromInt(123),
Photo: &InputFile{PathOrID: "file_id_test"},
}
_, err := SetChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteChatPhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteChatPhoto")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatPhotoParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatPhoto(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteChatPhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatPhotoParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteChatPhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatPhotoParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteChatPhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatPhotoParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteChatPhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatPhotoParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatPhoto(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteChatPhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteChatPhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteChatPhoto(context.Background(), bot, &DeleteChatPhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteChatPhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteChatPhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatPhotoParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteChatPhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteChatPhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatPhotoParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatPhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatTitle_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatTitle")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatTitleParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
}
_, err := SetChatTitle(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatTitle_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatTitleParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
}
_, err := SetChatTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatTitle_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatTitleParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
}
_, err := SetChatTitle(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatTitle_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatTitleParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
}
_, err := SetChatTitle(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatTitle_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatTitleParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
}
_, err := SetChatTitle(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatTitle_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatTitle_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatTitle(context.Background(), bot, &SetChatTitleParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatTitle_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatTitle_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatTitleParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
}
_, err := SetChatTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatTitle_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatTitle_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatTitleParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
}
_, err := SetChatTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatDescription_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatDescription")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatDescriptionParams{
ChatID: ChatIDFromInt(123),
}
_, err := SetChatDescription(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatDescription_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatDescriptionParams{
ChatID: ChatIDFromInt(123),
}
_, err := SetChatDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatDescription_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatDescriptionParams{
ChatID: ChatIDFromInt(123),
}
_, err := SetChatDescription(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatDescription_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatDescriptionParams{
ChatID: ChatIDFromInt(123),
}
_, err := SetChatDescription(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatDescription_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatDescriptionParams{
ChatID: ChatIDFromInt(123),
}
_, err := SetChatDescription(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatDescription_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatDescription(context.Background(), bot, &SetChatDescriptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatDescription_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatDescriptionParams{
ChatID: ChatIDFromInt(123),
}
_, err := SetChatDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatDescription_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatDescriptionParams{
ChatID: ChatIDFromInt(123),
}
_, err := SetChatDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_PinChatMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/pinChatMessage")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PinChatMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := PinChatMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_PinChatMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PinChatMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := PinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_PinChatMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PinChatMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := PinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_PinChatMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PinChatMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := PinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_PinChatMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PinChatMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := PinChatMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_PinChatMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_PinChatMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := PinChatMessage(context.Background(), bot, &PinChatMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_PinChatMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_PinChatMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PinChatMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := PinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_PinChatMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_PinChatMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PinChatMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := PinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UnpinChatMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/unpinChatMessage")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinChatMessageParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinChatMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UnpinChatMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinChatMessageParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UnpinChatMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinChatMessageParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UnpinChatMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinChatMessageParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UnpinChatMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinChatMessageParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinChatMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UnpinChatMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinChatMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UnpinChatMessage(context.Background(), bot, &UnpinChatMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UnpinChatMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinChatMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinChatMessageParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UnpinChatMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinChatMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinChatMessageParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinChatMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UnpinAllChatMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/unpinAllChatMessages")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllChatMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllChatMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UnpinAllChatMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllChatMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UnpinAllChatMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllChatMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UnpinAllChatMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllChatMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllChatMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UnpinAllChatMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllChatMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllChatMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UnpinAllChatMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinAllChatMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UnpinAllChatMessages(context.Background(), bot, &UnpinAllChatMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UnpinAllChatMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinAllChatMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllChatMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UnpinAllChatMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinAllChatMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllChatMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_LeaveChat_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/leaveChat")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &LeaveChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := LeaveChat(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_LeaveChat_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &LeaveChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := LeaveChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_LeaveChat_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &LeaveChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := LeaveChat(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_LeaveChat_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &LeaveChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := LeaveChat(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_LeaveChat_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &LeaveChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := LeaveChat(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_LeaveChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_LeaveChat_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := LeaveChat(context.Background(), bot, &LeaveChatParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_LeaveChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_LeaveChat_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &LeaveChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := LeaveChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_LeaveChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_LeaveChat_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &LeaveChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := LeaveChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetChat_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getChat")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChat(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetChat_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetChat_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChat(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetChat_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChat(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetChat_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChat(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChat_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetChat(context.Background(), bot, &GetChatParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChat_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChat_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetChatAdministrators_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getChatAdministrators")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatAdministratorsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatAdministrators(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetChatAdministrators_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatAdministratorsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatAdministrators(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetChatAdministrators_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatAdministratorsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatAdministrators(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetChatAdministrators_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatAdministratorsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatAdministrators(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetChatAdministrators_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatAdministratorsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatAdministrators(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetChatAdministrators_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatAdministrators_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetChatAdministrators(context.Background(), bot, &GetChatAdministratorsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetChatAdministrators_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatAdministrators_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatAdministratorsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatAdministrators(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetChatAdministrators_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatAdministrators_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatAdministratorsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatAdministrators(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetChatMemberCount_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getChatMemberCount")
})).Return(genTestResp(200, `{"ok":true,"result":0}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberCountParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatMemberCount(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetChatMemberCount_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberCountParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatMemberCount(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetChatMemberCount_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberCountParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatMemberCount(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetChatMemberCount_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberCountParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatMemberCount(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetChatMemberCount_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberCountParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatMemberCount(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetChatMemberCount_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatMemberCount_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetChatMemberCount(context.Background(), bot, &GetChatMemberCountParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetChatMemberCount_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatMemberCount_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberCountParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatMemberCount(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetChatMemberCount_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatMemberCount_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberCountParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatMemberCount(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetChatMember_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getChatMember")
})).Return(genTestResp(200, `{"ok":true,"result":{"status":"administrator"}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetChatMember(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetChatMember_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetChatMember_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetChatMember(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetChatMember_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetChatMember(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetChatMember_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetChatMember(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetChatMember_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatMember_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetChatMember(context.Background(), bot, &GetChatMemberParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetChatMember_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatMember_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetChatMember_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatMember_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMemberParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetChatMember(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetUserPersonalChatMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getUserPersonalChatMessages")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserPersonalChatMessagesParams{
UserID: 42,
Limit: 42,
}
_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetUserPersonalChatMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserPersonalChatMessagesParams{
UserID: 42,
Limit: 42,
}
_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetUserPersonalChatMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserPersonalChatMessagesParams{
UserID: 42,
Limit: 42,
}
_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetUserPersonalChatMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserPersonalChatMessagesParams{
UserID: 42,
Limit: 42,
}
_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetUserPersonalChatMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserPersonalChatMessagesParams{
UserID: 42,
Limit: 42,
}
_, err := GetUserPersonalChatMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetUserPersonalChatMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserPersonalChatMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetUserPersonalChatMessages(context.Background(), bot, &GetUserPersonalChatMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetUserPersonalChatMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserPersonalChatMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserPersonalChatMessagesParams{
UserID: 42,
Limit: 42,
}
_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetUserPersonalChatMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserPersonalChatMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserPersonalChatMessagesParams{
UserID: 42,
Limit: 42,
}
_, err := GetUserPersonalChatMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatStickerSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatStickerSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatStickerSetParams{
ChatID: ChatIDFromInt(123),
StickerSetName: "test_value",
}
_, err := SetChatStickerSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatStickerSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatStickerSetParams{
ChatID: ChatIDFromInt(123),
StickerSetName: "test_value",
}
_, err := SetChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatStickerSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatStickerSetParams{
ChatID: ChatIDFromInt(123),
StickerSetName: "test_value",
}
_, err := SetChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatStickerSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatStickerSetParams{
ChatID: ChatIDFromInt(123),
StickerSetName: "test_value",
}
_, err := SetChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatStickerSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatStickerSetParams{
ChatID: ChatIDFromInt(123),
StickerSetName: "test_value",
}
_, err := SetChatStickerSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatStickerSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatStickerSet(context.Background(), bot, &SetChatStickerSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatStickerSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatStickerSetParams{
ChatID: ChatIDFromInt(123),
StickerSetName: "test_value",
}
_, err := SetChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatStickerSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatStickerSetParams{
ChatID: ChatIDFromInt(123),
StickerSetName: "test_value",
}
_, err := SetChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteChatStickerSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteChatStickerSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatStickerSetParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatStickerSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteChatStickerSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatStickerSetParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteChatStickerSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatStickerSetParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteChatStickerSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatStickerSetParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteChatStickerSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatStickerSetParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatStickerSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteChatStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteChatStickerSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteChatStickerSet(context.Background(), bot, &DeleteChatStickerSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteChatStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteChatStickerSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatStickerSetParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteChatStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteChatStickerSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteChatStickerSetParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteChatStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetForumTopicIconStickers_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getForumTopicIconStickers")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
require.NoError(t, err)
}
func Test_GetForumTopicIconStickers_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetForumTopicIconStickers_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetForumTopicIconStickers_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetForumTopicIconStickers_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetForumTopicIconStickers(ctx, bot, &GetForumTopicIconStickersParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetForumTopicIconStickers_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetForumTopicIconStickers_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetForumTopicIconStickers_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetForumTopicIconStickers_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetForumTopicIconStickers_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetForumTopicIconStickers_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetForumTopicIconStickers(context.Background(), bot, &GetForumTopicIconStickersParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CreateForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/createForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := CreateForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CreateForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := CreateForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CreateForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := CreateForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CreateForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := CreateForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CreateForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := CreateForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CreateForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CreateForumTopic(context.Background(), bot, &CreateForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CreateForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := CreateForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CreateForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := CreateForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := EditForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := EditForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := EditForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := EditForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := EditForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditForumTopic(context.Background(), bot, &EditForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := EditForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := EditForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CloseForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/closeForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := CloseForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CloseForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := CloseForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CloseForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := CloseForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CloseForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := CloseForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CloseForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := CloseForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CloseForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CloseForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CloseForumTopic(context.Background(), bot, &CloseForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CloseForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CloseForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := CloseForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CloseForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CloseForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := CloseForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ReopenForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/reopenForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := ReopenForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ReopenForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := ReopenForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ReopenForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := ReopenForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ReopenForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := ReopenForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ReopenForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := ReopenForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ReopenForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReopenForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ReopenForumTopic(context.Background(), bot, &ReopenForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ReopenForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReopenForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := ReopenForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ReopenForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReopenForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := ReopenForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := DeleteForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := DeleteForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := DeleteForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := DeleteForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := DeleteForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteForumTopic(context.Background(), bot, &DeleteForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := DeleteForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteForumTopicParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := DeleteForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UnpinAllForumTopicMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/unpinAllForumTopicMessages")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UnpinAllForumTopicMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UnpinAllForumTopicMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UnpinAllForumTopicMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UnpinAllForumTopicMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := UnpinAllForumTopicMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UnpinAllForumTopicMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinAllForumTopicMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UnpinAllForumTopicMessages(context.Background(), bot, &UnpinAllForumTopicMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UnpinAllForumTopicMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinAllForumTopicMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UnpinAllForumTopicMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinAllForumTopicMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
MessageThreadID: 42,
}
_, err := UnpinAllForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditGeneralForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editGeneralForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := EditGeneralForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditGeneralForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := EditGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditGeneralForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := EditGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditGeneralForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := EditGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditGeneralForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := EditGeneralForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditGeneralForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditGeneralForumTopic(context.Background(), bot, &EditGeneralForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditGeneralForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := EditGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditGeneralForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
Name: "test_value",
}
_, err := EditGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CloseGeneralForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/closeGeneralForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := CloseGeneralForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CloseGeneralForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := CloseGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CloseGeneralForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := CloseGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CloseGeneralForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := CloseGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CloseGeneralForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := CloseGeneralForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CloseGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CloseGeneralForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CloseGeneralForumTopic(context.Background(), bot, &CloseGeneralForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CloseGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CloseGeneralForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := CloseGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CloseGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CloseGeneralForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CloseGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := CloseGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ReopenGeneralForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/reopenGeneralForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ReopenGeneralForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ReopenGeneralForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ReopenGeneralForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ReopenGeneralForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := ReopenGeneralForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ReopenGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReopenGeneralForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ReopenGeneralForumTopic(context.Background(), bot, &ReopenGeneralForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ReopenGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReopenGeneralForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ReopenGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReopenGeneralForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReopenGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := ReopenGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_HideGeneralForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/hideGeneralForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &HideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := HideGeneralForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_HideGeneralForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &HideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := HideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_HideGeneralForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &HideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := HideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_HideGeneralForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &HideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := HideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_HideGeneralForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &HideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := HideGeneralForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_HideGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_HideGeneralForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := HideGeneralForumTopic(context.Background(), bot, &HideGeneralForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_HideGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_HideGeneralForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &HideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := HideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_HideGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_HideGeneralForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &HideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := HideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UnhideGeneralForumTopic_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/unhideGeneralForumTopic")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnhideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UnhideGeneralForumTopic_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnhideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UnhideGeneralForumTopic_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnhideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UnhideGeneralForumTopic_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnhideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UnhideGeneralForumTopic_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnhideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnhideGeneralForumTopic(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UnhideGeneralForumTopic_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnhideGeneralForumTopic_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UnhideGeneralForumTopic(context.Background(), bot, &UnhideGeneralForumTopicParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UnhideGeneralForumTopic_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnhideGeneralForumTopic_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnhideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UnhideGeneralForumTopic_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnhideGeneralForumTopic_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnhideGeneralForumTopicParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnhideGeneralForumTopic(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UnpinAllGeneralForumTopicMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/unpinAllGeneralForumTopicMessages")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllGeneralForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UnpinAllGeneralForumTopicMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllGeneralForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UnpinAllGeneralForumTopicMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllGeneralForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UnpinAllGeneralForumTopicMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllGeneralForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UnpinAllGeneralForumTopicMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllGeneralForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllGeneralForumTopicMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UnpinAllGeneralForumTopicMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UnpinAllGeneralForumTopicMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, &UnpinAllGeneralForumTopicMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UnpinAllGeneralForumTopicMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UnpinAllGeneralForumTopicMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllGeneralForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UnpinAllGeneralForumTopicMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UnpinAllGeneralForumTopicMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UnpinAllGeneralForumTopicMessagesParams{
ChatID: ChatIDFromInt(123),
}
_, err := UnpinAllGeneralForumTopicMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AnswerCallbackQuery_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/answerCallbackQuery")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerCallbackQueryParams{
CallbackQueryID: "test_value",
}
_, err := AnswerCallbackQuery(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AnswerCallbackQuery_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerCallbackQueryParams{
CallbackQueryID: "test_value",
}
_, err := AnswerCallbackQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AnswerCallbackQuery_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerCallbackQueryParams{
CallbackQueryID: "test_value",
}
_, err := AnswerCallbackQuery(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AnswerCallbackQuery_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerCallbackQueryParams{
CallbackQueryID: "test_value",
}
_, err := AnswerCallbackQuery(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AnswerCallbackQuery_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerCallbackQueryParams{
CallbackQueryID: "test_value",
}
_, err := AnswerCallbackQuery(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AnswerCallbackQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerCallbackQuery_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AnswerCallbackQuery(context.Background(), bot, &AnswerCallbackQueryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AnswerCallbackQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerCallbackQuery_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerCallbackQueryParams{
CallbackQueryID: "test_value",
}
_, err := AnswerCallbackQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AnswerCallbackQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerCallbackQuery_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerCallbackQueryParams{
CallbackQueryID: "test_value",
}
_, err := AnswerCallbackQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AnswerGuestQuery_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/answerGuestQuery")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerGuestQueryParams{
GuestQueryID: "test_value",
Result: nil,
}
_, err := AnswerGuestQuery(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AnswerGuestQuery_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerGuestQueryParams{
GuestQueryID: "test_value",
Result: nil,
}
_, err := AnswerGuestQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AnswerGuestQuery_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerGuestQueryParams{
GuestQueryID: "test_value",
Result: nil,
}
_, err := AnswerGuestQuery(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AnswerGuestQuery_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerGuestQueryParams{
GuestQueryID: "test_value",
Result: nil,
}
_, err := AnswerGuestQuery(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AnswerGuestQuery_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerGuestQueryParams{
GuestQueryID: "test_value",
Result: nil,
}
_, err := AnswerGuestQuery(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AnswerGuestQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerGuestQuery_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AnswerGuestQuery(context.Background(), bot, &AnswerGuestQueryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AnswerGuestQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerGuestQuery_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerGuestQueryParams{
GuestQueryID: "test_value",
Result: nil,
}
_, err := AnswerGuestQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AnswerGuestQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerGuestQuery_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerGuestQueryParams{
GuestQueryID: "test_value",
Result: nil,
}
_, err := AnswerGuestQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetUserChatBoosts_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getUserChatBoosts")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserChatBoostsParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetUserChatBoosts(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetUserChatBoosts_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserChatBoostsParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetUserChatBoosts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetUserChatBoosts_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserChatBoostsParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetUserChatBoosts(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetUserChatBoosts_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserChatBoostsParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetUserChatBoosts(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetUserChatBoosts_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserChatBoostsParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetUserChatBoosts(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetUserChatBoosts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserChatBoosts_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetUserChatBoosts(context.Background(), bot, &GetUserChatBoostsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetUserChatBoosts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserChatBoosts_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserChatBoostsParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetUserChatBoosts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetUserChatBoosts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserChatBoosts_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserChatBoostsParams{
ChatID: ChatIDFromInt(123),
UserID: 42,
}
_, err := GetUserChatBoosts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetBusinessConnection_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getBusinessConnection")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessConnectionParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessConnection(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetBusinessConnection_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessConnectionParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessConnection(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetBusinessConnection_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessConnectionParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessConnection(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetBusinessConnection_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessConnectionParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessConnection(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetBusinessConnection_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessConnectionParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessConnection(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetBusinessConnection_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetBusinessConnection_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetBusinessConnection(context.Background(), bot, &GetBusinessConnectionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetBusinessConnection_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetBusinessConnection_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessConnectionParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessConnection(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetBusinessConnection_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetBusinessConnection_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessConnectionParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessConnection(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetManagedBotToken_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getManagedBotToken")
})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotTokenParams{
UserID: 42,
}
_, err := GetManagedBotToken(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetManagedBotToken_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotTokenParams{
UserID: 42,
}
_, err := GetManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetManagedBotToken_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotTokenParams{
UserID: 42,
}
_, err := GetManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetManagedBotToken_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotTokenParams{
UserID: 42,
}
_, err := GetManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetManagedBotToken_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotTokenParams{
UserID: 42,
}
_, err := GetManagedBotToken(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetManagedBotToken_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetManagedBotToken_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetManagedBotToken(context.Background(), bot, &GetManagedBotTokenParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetManagedBotToken_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetManagedBotToken_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotTokenParams{
UserID: 42,
}
_, err := GetManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetManagedBotToken_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetManagedBotToken_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotTokenParams{
UserID: 42,
}
_, err := GetManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ReplaceManagedBotToken_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/replaceManagedBotToken")
})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceManagedBotTokenParams{
UserID: 42,
}
_, err := ReplaceManagedBotToken(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ReplaceManagedBotToken_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceManagedBotTokenParams{
UserID: 42,
}
_, err := ReplaceManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ReplaceManagedBotToken_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceManagedBotTokenParams{
UserID: 42,
}
_, err := ReplaceManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ReplaceManagedBotToken_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceManagedBotTokenParams{
UserID: 42,
}
_, err := ReplaceManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ReplaceManagedBotToken_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceManagedBotTokenParams{
UserID: 42,
}
_, err := ReplaceManagedBotToken(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ReplaceManagedBotToken_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReplaceManagedBotToken_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ReplaceManagedBotToken(context.Background(), bot, &ReplaceManagedBotTokenParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ReplaceManagedBotToken_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReplaceManagedBotToken_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceManagedBotTokenParams{
UserID: 42,
}
_, err := ReplaceManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ReplaceManagedBotToken_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReplaceManagedBotToken_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceManagedBotTokenParams{
UserID: 42,
}
_, err := ReplaceManagedBotToken(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetManagedBotAccessSettings_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getManagedBotAccessSettings")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotAccessSettingsParams{
UserID: 42,
}
_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetManagedBotAccessSettings_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotAccessSettingsParams{
UserID: 42,
}
_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetManagedBotAccessSettings_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotAccessSettingsParams{
UserID: 42,
}
_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetManagedBotAccessSettings_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotAccessSettingsParams{
UserID: 42,
}
_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetManagedBotAccessSettings_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotAccessSettingsParams{
UserID: 42,
}
_, err := GetManagedBotAccessSettings(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetManagedBotAccessSettings_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetManagedBotAccessSettings_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetManagedBotAccessSettings(context.Background(), bot, &GetManagedBotAccessSettingsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetManagedBotAccessSettings_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetManagedBotAccessSettings_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotAccessSettingsParams{
UserID: 42,
}
_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetManagedBotAccessSettings_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetManagedBotAccessSettings_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetManagedBotAccessSettingsParams{
UserID: 42,
}
_, err := GetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetManagedBotAccessSettings_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setManagedBotAccessSettings")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetManagedBotAccessSettingsParams{
UserID: 42,
IsAccessRestricted: true,
}
_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetManagedBotAccessSettings_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetManagedBotAccessSettingsParams{
UserID: 42,
IsAccessRestricted: true,
}
_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetManagedBotAccessSettings_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetManagedBotAccessSettingsParams{
UserID: 42,
IsAccessRestricted: true,
}
_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetManagedBotAccessSettings_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetManagedBotAccessSettingsParams{
UserID: 42,
IsAccessRestricted: true,
}
_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetManagedBotAccessSettings_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetManagedBotAccessSettingsParams{
UserID: 42,
IsAccessRestricted: true,
}
_, err := SetManagedBotAccessSettings(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetManagedBotAccessSettings_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetManagedBotAccessSettings_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetManagedBotAccessSettings(context.Background(), bot, &SetManagedBotAccessSettingsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetManagedBotAccessSettings_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetManagedBotAccessSettings_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetManagedBotAccessSettingsParams{
UserID: 42,
IsAccessRestricted: true,
}
_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetManagedBotAccessSettings_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetManagedBotAccessSettings_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetManagedBotAccessSettingsParams{
UserID: 42,
IsAccessRestricted: true,
}
_, err := SetManagedBotAccessSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetMyCommands_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setMyCommands")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyCommandsParams{
Commands: nil,
}
_, err := SetMyCommands(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetMyCommands_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyCommandsParams{
Commands: nil,
}
_, err := SetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetMyCommands_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyCommandsParams{
Commands: nil,
}
_, err := SetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetMyCommands_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyCommandsParams{
Commands: nil,
}
_, err := SetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetMyCommands_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyCommandsParams{
Commands: nil,
}
_, err := SetMyCommands(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetMyCommands_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyCommands_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetMyCommands(context.Background(), bot, &SetMyCommandsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetMyCommands_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyCommands_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyCommandsParams{
Commands: nil,
}
_, err := SetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetMyCommands_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyCommands_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyCommandsParams{
Commands: nil,
}
_, err := SetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteMyCommands_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteMyCommands")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMyCommandsParams{}
_, err := DeleteMyCommands(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteMyCommands_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMyCommandsParams{}
_, err := DeleteMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteMyCommands_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMyCommandsParams{}
_, err := DeleteMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteMyCommands_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMyCommandsParams{}
_, err := DeleteMyCommands(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteMyCommands_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMyCommandsParams{}
_, err := DeleteMyCommands(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteMyCommands_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMyCommands_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteMyCommands(context.Background(), bot, &DeleteMyCommandsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMyCommands_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMyCommands_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMyCommandsParams{}
_, err := DeleteMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMyCommands_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMyCommands_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMyCommandsParams{}
_, err := DeleteMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetMyCommands_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getMyCommands")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyCommandsParams{}
_, err := GetMyCommands(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetMyCommands_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyCommandsParams{}
_, err := GetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetMyCommands_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyCommandsParams{}
_, err := GetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetMyCommands_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyCommandsParams{}
_, err := GetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetMyCommands_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyCommandsParams{}
_, err := GetMyCommands(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetMyCommands_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyCommands_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetMyCommands(context.Background(), bot, &GetMyCommandsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetMyCommands_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyCommands_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyCommandsParams{}
_, err := GetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetMyCommands_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyCommands_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyCommandsParams{}
_, err := GetMyCommands(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetMyName_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setMyName")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyNameParams{}
_, err := SetMyName(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetMyName_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyNameParams{}
_, err := SetMyName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetMyName_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyNameParams{}
_, err := SetMyName(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetMyName_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyNameParams{}
_, err := SetMyName(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetMyName_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyNameParams{}
_, err := SetMyName(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetMyName_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyName_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetMyName(context.Background(), bot, &SetMyNameParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetMyName_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyName_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyNameParams{}
_, err := SetMyName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetMyName_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyName_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyNameParams{}
_, err := SetMyName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetMyName_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getMyName")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyNameParams{}
_, err := GetMyName(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetMyName_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyNameParams{}
_, err := GetMyName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetMyName_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyNameParams{}
_, err := GetMyName(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetMyName_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyNameParams{}
_, err := GetMyName(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetMyName_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyNameParams{}
_, err := GetMyName(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetMyName_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyName_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetMyName(context.Background(), bot, &GetMyNameParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetMyName_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyName_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyNameParams{}
_, err := GetMyName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetMyName_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyName_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyNameParams{}
_, err := GetMyName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetMyDescription_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setMyDescription")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDescriptionParams{}
_, err := SetMyDescription(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetMyDescription_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDescriptionParams{}
_, err := SetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetMyDescription_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDescriptionParams{}
_, err := SetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetMyDescription_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDescriptionParams{}
_, err := SetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetMyDescription_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDescriptionParams{}
_, err := SetMyDescription(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetMyDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyDescription_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetMyDescription(context.Background(), bot, &SetMyDescriptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetMyDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyDescription_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDescriptionParams{}
_, err := SetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetMyDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyDescription_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDescriptionParams{}
_, err := SetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetMyDescription_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getMyDescription")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDescriptionParams{}
_, err := GetMyDescription(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetMyDescription_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDescriptionParams{}
_, err := GetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetMyDescription_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDescriptionParams{}
_, err := GetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetMyDescription_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDescriptionParams{}
_, err := GetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetMyDescription_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDescriptionParams{}
_, err := GetMyDescription(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetMyDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyDescription_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetMyDescription(context.Background(), bot, &GetMyDescriptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetMyDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyDescription_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDescriptionParams{}
_, err := GetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetMyDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyDescription_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDescriptionParams{}
_, err := GetMyDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetMyShortDescription_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setMyShortDescription")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyShortDescriptionParams{}
_, err := SetMyShortDescription(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetMyShortDescription_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyShortDescriptionParams{}
_, err := SetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetMyShortDescription_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyShortDescriptionParams{}
_, err := SetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetMyShortDescription_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyShortDescriptionParams{}
_, err := SetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetMyShortDescription_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyShortDescriptionParams{}
_, err := SetMyShortDescription(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetMyShortDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyShortDescription_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetMyShortDescription(context.Background(), bot, &SetMyShortDescriptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetMyShortDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyShortDescription_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyShortDescriptionParams{}
_, err := SetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetMyShortDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyShortDescription_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyShortDescriptionParams{}
_, err := SetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetMyShortDescription_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getMyShortDescription")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyShortDescriptionParams{}
_, err := GetMyShortDescription(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetMyShortDescription_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyShortDescriptionParams{}
_, err := GetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetMyShortDescription_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyShortDescriptionParams{}
_, err := GetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetMyShortDescription_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyShortDescriptionParams{}
_, err := GetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetMyShortDescription_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyShortDescriptionParams{}
_, err := GetMyShortDescription(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetMyShortDescription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyShortDescription_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetMyShortDescription(context.Background(), bot, &GetMyShortDescriptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetMyShortDescription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyShortDescription_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyShortDescriptionParams{}
_, err := GetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetMyShortDescription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyShortDescription_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyShortDescriptionParams{}
_, err := GetMyShortDescription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetMyProfilePhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setMyProfilePhoto")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyProfilePhotoParams{
Photo: nil,
}
_, err := SetMyProfilePhoto(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetMyProfilePhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyProfilePhotoParams{
Photo: nil,
}
_, err := SetMyProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetMyProfilePhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyProfilePhotoParams{
Photo: nil,
}
_, err := SetMyProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetMyProfilePhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyProfilePhotoParams{
Photo: nil,
}
_, err := SetMyProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetMyProfilePhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyProfilePhotoParams{
Photo: nil,
}
_, err := SetMyProfilePhoto(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetMyProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyProfilePhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetMyProfilePhoto(context.Background(), bot, &SetMyProfilePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetMyProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyProfilePhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyProfilePhotoParams{
Photo: nil,
}
_, err := SetMyProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetMyProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyProfilePhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyProfilePhotoParams{
Photo: nil,
}
_, err := SetMyProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RemoveMyProfilePhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/removeMyProfilePhoto")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
require.NoError(t, err)
}
func Test_RemoveMyProfilePhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RemoveMyProfilePhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RemoveMyProfilePhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RemoveMyProfilePhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := RemoveMyProfilePhoto(ctx, bot, &RemoveMyProfilePhotoParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RemoveMyProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveMyProfilePhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RemoveMyProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveMyProfilePhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RemoveMyProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveMyProfilePhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := RemoveMyProfilePhoto(context.Background(), bot, &RemoveMyProfilePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetChatMenuButton_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setChatMenuButton")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMenuButtonParams{}
_, err := SetChatMenuButton(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetChatMenuButton_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMenuButtonParams{}
_, err := SetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetChatMenuButton_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMenuButtonParams{}
_, err := SetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetChatMenuButton_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMenuButtonParams{}
_, err := SetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetChatMenuButton_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMenuButtonParams{}
_, err := SetChatMenuButton(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetChatMenuButton_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetChatMenuButton_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetChatMenuButton(context.Background(), bot, &SetChatMenuButtonParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetChatMenuButton_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetChatMenuButton_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMenuButtonParams{}
_, err := SetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetChatMenuButton_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetChatMenuButton_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetChatMenuButtonParams{}
_, err := SetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetChatMenuButton_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getChatMenuButton")
})).Return(genTestResp(200, `{"ok":true,"result":{"type":"commands"}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMenuButtonParams{}
_, err := GetChatMenuButton(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetChatMenuButton_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMenuButtonParams{}
_, err := GetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetChatMenuButton_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMenuButtonParams{}
_, err := GetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetChatMenuButton_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMenuButtonParams{}
_, err := GetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetChatMenuButton_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMenuButtonParams{}
_, err := GetChatMenuButton(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetChatMenuButton_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatMenuButton_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetChatMenuButton(context.Background(), bot, &GetChatMenuButtonParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetChatMenuButton_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatMenuButton_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMenuButtonParams{}
_, err := GetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetChatMenuButton_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatMenuButton_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatMenuButtonParams{}
_, err := GetChatMenuButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetMyDefaultAdministratorRights_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setMyDefaultAdministratorRights")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDefaultAdministratorRightsParams{}
_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetMyDefaultAdministratorRights_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDefaultAdministratorRightsParams{}
_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetMyDefaultAdministratorRights_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDefaultAdministratorRightsParams{}
_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetMyDefaultAdministratorRights_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDefaultAdministratorRightsParams{}
_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetMyDefaultAdministratorRights_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDefaultAdministratorRightsParams{}
_, err := SetMyDefaultAdministratorRights(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetMyDefaultAdministratorRights_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetMyDefaultAdministratorRights_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetMyDefaultAdministratorRights(context.Background(), bot, &SetMyDefaultAdministratorRightsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetMyDefaultAdministratorRights_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetMyDefaultAdministratorRights_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDefaultAdministratorRightsParams{}
_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetMyDefaultAdministratorRights_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetMyDefaultAdministratorRights_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetMyDefaultAdministratorRightsParams{}
_, err := SetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetMyDefaultAdministratorRights_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getMyDefaultAdministratorRights")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDefaultAdministratorRightsParams{}
_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetMyDefaultAdministratorRights_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDefaultAdministratorRightsParams{}
_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetMyDefaultAdministratorRights_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDefaultAdministratorRightsParams{}
_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetMyDefaultAdministratorRights_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDefaultAdministratorRightsParams{}
_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetMyDefaultAdministratorRights_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDefaultAdministratorRightsParams{}
_, err := GetMyDefaultAdministratorRights(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetMyDefaultAdministratorRights_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyDefaultAdministratorRights_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetMyDefaultAdministratorRights(context.Background(), bot, &GetMyDefaultAdministratorRightsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetMyDefaultAdministratorRights_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyDefaultAdministratorRights_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDefaultAdministratorRightsParams{}
_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetMyDefaultAdministratorRights_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyDefaultAdministratorRights_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetMyDefaultAdministratorRightsParams{}
_, err := GetMyDefaultAdministratorRights(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetAvailableGifts_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getAvailableGifts")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
require.NoError(t, err)
}
func Test_GetAvailableGifts_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetAvailableGifts_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetAvailableGifts_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetAvailableGifts_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetAvailableGifts(ctx, bot, &GetAvailableGiftsParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetAvailableGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetAvailableGifts_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetAvailableGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetAvailableGifts_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetAvailableGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetAvailableGifts_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetAvailableGifts(context.Background(), bot, &GetAvailableGiftsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendGift_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendGift")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGiftParams{
GiftID: "test_value",
}
_, err := SendGift(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendGift_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGiftParams{
GiftID: "test_value",
}
_, err := SendGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendGift_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGiftParams{
GiftID: "test_value",
}
_, err := SendGift(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendGift_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGiftParams{
GiftID: "test_value",
}
_, err := SendGift(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendGift_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGiftParams{
GiftID: "test_value",
}
_, err := SendGift(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendGift_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendGift_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendGift(context.Background(), bot, &SendGiftParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendGift_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendGift_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGiftParams{
GiftID: "test_value",
}
_, err := SendGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendGift_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendGift_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGiftParams{
GiftID: "test_value",
}
_, err := SendGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GiftPremiumSubscription_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/giftPremiumSubscription")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GiftPremiumSubscriptionParams{
UserID: 42,
MonthCount: 42,
StarCount: 42,
}
_, err := GiftPremiumSubscription(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GiftPremiumSubscription_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GiftPremiumSubscriptionParams{
UserID: 42,
MonthCount: 42,
StarCount: 42,
}
_, err := GiftPremiumSubscription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GiftPremiumSubscription_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GiftPremiumSubscriptionParams{
UserID: 42,
MonthCount: 42,
StarCount: 42,
}
_, err := GiftPremiumSubscription(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GiftPremiumSubscription_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GiftPremiumSubscriptionParams{
UserID: 42,
MonthCount: 42,
StarCount: 42,
}
_, err := GiftPremiumSubscription(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GiftPremiumSubscription_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GiftPremiumSubscriptionParams{
UserID: 42,
MonthCount: 42,
StarCount: 42,
}
_, err := GiftPremiumSubscription(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GiftPremiumSubscription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GiftPremiumSubscription_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GiftPremiumSubscription(context.Background(), bot, &GiftPremiumSubscriptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GiftPremiumSubscription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GiftPremiumSubscription_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GiftPremiumSubscriptionParams{
UserID: 42,
MonthCount: 42,
StarCount: 42,
}
_, err := GiftPremiumSubscription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GiftPremiumSubscription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GiftPremiumSubscription_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GiftPremiumSubscriptionParams{
UserID: 42,
MonthCount: 42,
StarCount: 42,
}
_, err := GiftPremiumSubscription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_VerifyUser_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/verifyUser")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyUserParams{
UserID: 42,
}
_, err := VerifyUser(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_VerifyUser_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyUserParams{
UserID: 42,
}
_, err := VerifyUser(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_VerifyUser_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyUserParams{
UserID: 42,
}
_, err := VerifyUser(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_VerifyUser_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyUserParams{
UserID: 42,
}
_, err := VerifyUser(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_VerifyUser_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyUserParams{
UserID: 42,
}
_, err := VerifyUser(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_VerifyUser_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_VerifyUser_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := VerifyUser(context.Background(), bot, &VerifyUserParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_VerifyUser_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_VerifyUser_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyUserParams{
UserID: 42,
}
_, err := VerifyUser(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_VerifyUser_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_VerifyUser_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyUserParams{
UserID: 42,
}
_, err := VerifyUser(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_VerifyChat_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/verifyChat")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := VerifyChat(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_VerifyChat_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := VerifyChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_VerifyChat_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := VerifyChat(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_VerifyChat_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := VerifyChat(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_VerifyChat_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := VerifyChat(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_VerifyChat_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_VerifyChat_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := VerifyChat(context.Background(), bot, &VerifyChatParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_VerifyChat_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_VerifyChat_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := VerifyChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_VerifyChat_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_VerifyChat_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &VerifyChatParams{
ChatID: ChatIDFromInt(123),
}
_, err := VerifyChat(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RemoveUserVerification_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/removeUserVerification")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveUserVerificationParams{
UserID: 42,
}
_, err := RemoveUserVerification(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_RemoveUserVerification_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveUserVerificationParams{
UserID: 42,
}
_, err := RemoveUserVerification(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RemoveUserVerification_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveUserVerificationParams{
UserID: 42,
}
_, err := RemoveUserVerification(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RemoveUserVerification_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveUserVerificationParams{
UserID: 42,
}
_, err := RemoveUserVerification(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RemoveUserVerification_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveUserVerificationParams{
UserID: 42,
}
_, err := RemoveUserVerification(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RemoveUserVerification_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveUserVerification_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RemoveUserVerification(context.Background(), bot, &RemoveUserVerificationParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RemoveUserVerification_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveUserVerification_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveUserVerificationParams{
UserID: 42,
}
_, err := RemoveUserVerification(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RemoveUserVerification_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveUserVerification_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveUserVerificationParams{
UserID: 42,
}
_, err := RemoveUserVerification(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RemoveChatVerification_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/removeChatVerification")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveChatVerificationParams{
ChatID: ChatIDFromInt(123),
}
_, err := RemoveChatVerification(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_RemoveChatVerification_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveChatVerificationParams{
ChatID: ChatIDFromInt(123),
}
_, err := RemoveChatVerification(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RemoveChatVerification_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveChatVerificationParams{
ChatID: ChatIDFromInt(123),
}
_, err := RemoveChatVerification(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RemoveChatVerification_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveChatVerificationParams{
ChatID: ChatIDFromInt(123),
}
_, err := RemoveChatVerification(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RemoveChatVerification_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveChatVerificationParams{
ChatID: ChatIDFromInt(123),
}
_, err := RemoveChatVerification(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RemoveChatVerification_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveChatVerification_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RemoveChatVerification(context.Background(), bot, &RemoveChatVerificationParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RemoveChatVerification_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveChatVerification_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveChatVerificationParams{
ChatID: ChatIDFromInt(123),
}
_, err := RemoveChatVerification(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RemoveChatVerification_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveChatVerification_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveChatVerificationParams{
ChatID: ChatIDFromInt(123),
}
_, err := RemoveChatVerification(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ReadBusinessMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/readBusinessMessage")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReadBusinessMessageParams{
BusinessConnectionID: "test_value",
ChatID: 42,
MessageID: 42,
}
_, err := ReadBusinessMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ReadBusinessMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReadBusinessMessageParams{
BusinessConnectionID: "test_value",
ChatID: 42,
MessageID: 42,
}
_, err := ReadBusinessMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ReadBusinessMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReadBusinessMessageParams{
BusinessConnectionID: "test_value",
ChatID: 42,
MessageID: 42,
}
_, err := ReadBusinessMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ReadBusinessMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReadBusinessMessageParams{
BusinessConnectionID: "test_value",
ChatID: 42,
MessageID: 42,
}
_, err := ReadBusinessMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ReadBusinessMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReadBusinessMessageParams{
BusinessConnectionID: "test_value",
ChatID: 42,
MessageID: 42,
}
_, err := ReadBusinessMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ReadBusinessMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReadBusinessMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ReadBusinessMessage(context.Background(), bot, &ReadBusinessMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ReadBusinessMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReadBusinessMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReadBusinessMessageParams{
BusinessConnectionID: "test_value",
ChatID: 42,
MessageID: 42,
}
_, err := ReadBusinessMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ReadBusinessMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReadBusinessMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReadBusinessMessageParams{
BusinessConnectionID: "test_value",
ChatID: 42,
MessageID: 42,
}
_, err := ReadBusinessMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteBusinessMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteBusinessMessages")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteBusinessMessagesParams{
BusinessConnectionID: "test_value",
MessageIds: nil,
}
_, err := DeleteBusinessMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteBusinessMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteBusinessMessagesParams{
BusinessConnectionID: "test_value",
MessageIds: nil,
}
_, err := DeleteBusinessMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteBusinessMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteBusinessMessagesParams{
BusinessConnectionID: "test_value",
MessageIds: nil,
}
_, err := DeleteBusinessMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteBusinessMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteBusinessMessagesParams{
BusinessConnectionID: "test_value",
MessageIds: nil,
}
_, err := DeleteBusinessMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteBusinessMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteBusinessMessagesParams{
BusinessConnectionID: "test_value",
MessageIds: nil,
}
_, err := DeleteBusinessMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteBusinessMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteBusinessMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteBusinessMessages(context.Background(), bot, &DeleteBusinessMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteBusinessMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteBusinessMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteBusinessMessagesParams{
BusinessConnectionID: "test_value",
MessageIds: nil,
}
_, err := DeleteBusinessMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteBusinessMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteBusinessMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteBusinessMessagesParams{
BusinessConnectionID: "test_value",
MessageIds: nil,
}
_, err := DeleteBusinessMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetBusinessAccountName_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setBusinessAccountName")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountNameParams{
BusinessConnectionID: "test_value",
FirstName: "test_value",
}
_, err := SetBusinessAccountName(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetBusinessAccountName_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountNameParams{
BusinessConnectionID: "test_value",
FirstName: "test_value",
}
_, err := SetBusinessAccountName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetBusinessAccountName_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountNameParams{
BusinessConnectionID: "test_value",
FirstName: "test_value",
}
_, err := SetBusinessAccountName(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetBusinessAccountName_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountNameParams{
BusinessConnectionID: "test_value",
FirstName: "test_value",
}
_, err := SetBusinessAccountName(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetBusinessAccountName_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountNameParams{
BusinessConnectionID: "test_value",
FirstName: "test_value",
}
_, err := SetBusinessAccountName(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetBusinessAccountName_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountName_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetBusinessAccountName(context.Background(), bot, &SetBusinessAccountNameParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountName_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountName_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountNameParams{
BusinessConnectionID: "test_value",
FirstName: "test_value",
}
_, err := SetBusinessAccountName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountName_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountName_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountNameParams{
BusinessConnectionID: "test_value",
FirstName: "test_value",
}
_, err := SetBusinessAccountName(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetBusinessAccountUsername_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setBusinessAccountUsername")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountUsernameParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountUsername(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetBusinessAccountUsername_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountUsernameParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountUsername(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetBusinessAccountUsername_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountUsernameParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountUsername(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetBusinessAccountUsername_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountUsernameParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountUsername(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetBusinessAccountUsername_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountUsernameParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountUsername(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetBusinessAccountUsername_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountUsername_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetBusinessAccountUsername(context.Background(), bot, &SetBusinessAccountUsernameParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountUsername_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountUsername_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountUsernameParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountUsername(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountUsername_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountUsername_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountUsernameParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountUsername(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetBusinessAccountBio_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setBusinessAccountBio")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountBioParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountBio(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetBusinessAccountBio_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountBioParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountBio(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetBusinessAccountBio_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountBioParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountBio(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetBusinessAccountBio_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountBioParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountBio(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetBusinessAccountBio_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountBioParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountBio(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetBusinessAccountBio_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountBio_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetBusinessAccountBio(context.Background(), bot, &SetBusinessAccountBioParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountBio_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountBio_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountBioParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountBio(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountBio_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountBio_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountBioParams{
BusinessConnectionID: "test_value",
}
_, err := SetBusinessAccountBio(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetBusinessAccountProfilePhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setBusinessAccountProfilePhoto")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
Photo: nil,
}
_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetBusinessAccountProfilePhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
Photo: nil,
}
_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetBusinessAccountProfilePhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
Photo: nil,
}
_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetBusinessAccountProfilePhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
Photo: nil,
}
_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetBusinessAccountProfilePhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
Photo: nil,
}
_, err := SetBusinessAccountProfilePhoto(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetBusinessAccountProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountProfilePhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, &SetBusinessAccountProfilePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountProfilePhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
Photo: nil,
}
_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountProfilePhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
Photo: nil,
}
_, err := SetBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RemoveBusinessAccountProfilePhoto_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/removeBusinessAccountProfilePhoto")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
}
_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_RemoveBusinessAccountProfilePhoto_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
}
_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RemoveBusinessAccountProfilePhoto_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
}
_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RemoveBusinessAccountProfilePhoto_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
}
_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RemoveBusinessAccountProfilePhoto_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
}
_, err := RemoveBusinessAccountProfilePhoto(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RemoveBusinessAccountProfilePhoto_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RemoveBusinessAccountProfilePhoto_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, &RemoveBusinessAccountProfilePhotoParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RemoveBusinessAccountProfilePhoto_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RemoveBusinessAccountProfilePhoto_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
}
_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RemoveBusinessAccountProfilePhoto_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RemoveBusinessAccountProfilePhoto_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RemoveBusinessAccountProfilePhotoParams{
BusinessConnectionID: "test_value",
}
_, err := RemoveBusinessAccountProfilePhoto(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetBusinessAccountGiftSettings_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setBusinessAccountGiftSettings")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountGiftSettingsParams{
BusinessConnectionID: "test_value",
ShowGiftButton: true,
AcceptedGiftTypes: AcceptedGiftTypes{},
}
_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetBusinessAccountGiftSettings_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountGiftSettingsParams{
BusinessConnectionID: "test_value",
ShowGiftButton: true,
AcceptedGiftTypes: AcceptedGiftTypes{},
}
_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetBusinessAccountGiftSettings_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountGiftSettingsParams{
BusinessConnectionID: "test_value",
ShowGiftButton: true,
AcceptedGiftTypes: AcceptedGiftTypes{},
}
_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetBusinessAccountGiftSettings_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountGiftSettingsParams{
BusinessConnectionID: "test_value",
ShowGiftButton: true,
AcceptedGiftTypes: AcceptedGiftTypes{},
}
_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetBusinessAccountGiftSettings_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountGiftSettingsParams{
BusinessConnectionID: "test_value",
ShowGiftButton: true,
AcceptedGiftTypes: AcceptedGiftTypes{},
}
_, err := SetBusinessAccountGiftSettings(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetBusinessAccountGiftSettings_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetBusinessAccountGiftSettings_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetBusinessAccountGiftSettings(context.Background(), bot, &SetBusinessAccountGiftSettingsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountGiftSettings_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetBusinessAccountGiftSettings_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountGiftSettingsParams{
BusinessConnectionID: "test_value",
ShowGiftButton: true,
AcceptedGiftTypes: AcceptedGiftTypes{},
}
_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetBusinessAccountGiftSettings_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetBusinessAccountGiftSettings_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetBusinessAccountGiftSettingsParams{
BusinessConnectionID: "test_value",
ShowGiftButton: true,
AcceptedGiftTypes: AcceptedGiftTypes{},
}
_, err := SetBusinessAccountGiftSettings(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetBusinessAccountStarBalance_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getBusinessAccountStarBalance")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountStarBalanceParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetBusinessAccountStarBalance_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountStarBalanceParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetBusinessAccountStarBalance_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountStarBalanceParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetBusinessAccountStarBalance_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountStarBalanceParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetBusinessAccountStarBalance_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountStarBalanceParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountStarBalance(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetBusinessAccountStarBalance_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetBusinessAccountStarBalance_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetBusinessAccountStarBalance(context.Background(), bot, &GetBusinessAccountStarBalanceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetBusinessAccountStarBalance_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetBusinessAccountStarBalance_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountStarBalanceParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetBusinessAccountStarBalance_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetBusinessAccountStarBalance_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountStarBalanceParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountStarBalance(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_TransferBusinessAccountStars_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/transferBusinessAccountStars")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferBusinessAccountStarsParams{
BusinessConnectionID: "test_value",
StarCount: 42,
}
_, err := TransferBusinessAccountStars(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_TransferBusinessAccountStars_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferBusinessAccountStarsParams{
BusinessConnectionID: "test_value",
StarCount: 42,
}
_, err := TransferBusinessAccountStars(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_TransferBusinessAccountStars_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferBusinessAccountStarsParams{
BusinessConnectionID: "test_value",
StarCount: 42,
}
_, err := TransferBusinessAccountStars(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_TransferBusinessAccountStars_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferBusinessAccountStarsParams{
BusinessConnectionID: "test_value",
StarCount: 42,
}
_, err := TransferBusinessAccountStars(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_TransferBusinessAccountStars_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferBusinessAccountStarsParams{
BusinessConnectionID: "test_value",
StarCount: 42,
}
_, err := TransferBusinessAccountStars(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_TransferBusinessAccountStars_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_TransferBusinessAccountStars_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := TransferBusinessAccountStars(context.Background(), bot, &TransferBusinessAccountStarsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_TransferBusinessAccountStars_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_TransferBusinessAccountStars_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferBusinessAccountStarsParams{
BusinessConnectionID: "test_value",
StarCount: 42,
}
_, err := TransferBusinessAccountStars(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_TransferBusinessAccountStars_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_TransferBusinessAccountStars_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferBusinessAccountStarsParams{
BusinessConnectionID: "test_value",
StarCount: 42,
}
_, err := TransferBusinessAccountStars(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetBusinessAccountGifts_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getBusinessAccountGifts")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountGiftsParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountGifts(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetBusinessAccountGifts_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountGiftsParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetBusinessAccountGifts_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountGiftsParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountGifts(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetBusinessAccountGifts_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountGiftsParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountGifts(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetBusinessAccountGifts_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountGiftsParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountGifts(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetBusinessAccountGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetBusinessAccountGifts_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetBusinessAccountGifts(context.Background(), bot, &GetBusinessAccountGiftsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetBusinessAccountGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetBusinessAccountGifts_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountGiftsParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetBusinessAccountGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetBusinessAccountGifts_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetBusinessAccountGiftsParams{
BusinessConnectionID: "test_value",
}
_, err := GetBusinessAccountGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetUserGifts_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getUserGifts")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserGiftsParams{
UserID: 42,
}
_, err := GetUserGifts(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetUserGifts_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserGiftsParams{
UserID: 42,
}
_, err := GetUserGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetUserGifts_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserGiftsParams{
UserID: 42,
}
_, err := GetUserGifts(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetUserGifts_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserGiftsParams{
UserID: 42,
}
_, err := GetUserGifts(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetUserGifts_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserGiftsParams{
UserID: 42,
}
_, err := GetUserGifts(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetUserGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetUserGifts_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetUserGifts(context.Background(), bot, &GetUserGiftsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetUserGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetUserGifts_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserGiftsParams{
UserID: 42,
}
_, err := GetUserGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetUserGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetUserGifts_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetUserGiftsParams{
UserID: 42,
}
_, err := GetUserGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetChatGifts_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getChatGifts")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatGiftsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatGifts(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetChatGifts_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatGiftsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetChatGifts_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatGiftsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatGifts(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetChatGifts_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatGiftsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatGifts(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetChatGifts_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatGiftsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatGifts(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetChatGifts_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetChatGifts_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetChatGifts(context.Background(), bot, &GetChatGiftsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetChatGifts_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetChatGifts_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatGiftsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetChatGifts_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetChatGifts_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetChatGiftsParams{
ChatID: ChatIDFromInt(123),
}
_, err := GetChatGifts(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ConvertGiftToStars_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/convertGiftToStars")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ConvertGiftToStarsParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := ConvertGiftToStars(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ConvertGiftToStars_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ConvertGiftToStarsParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := ConvertGiftToStars(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ConvertGiftToStars_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ConvertGiftToStarsParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := ConvertGiftToStars(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ConvertGiftToStars_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ConvertGiftToStarsParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := ConvertGiftToStars(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ConvertGiftToStars_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ConvertGiftToStarsParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := ConvertGiftToStars(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ConvertGiftToStars_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ConvertGiftToStars_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ConvertGiftToStars(context.Background(), bot, &ConvertGiftToStarsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ConvertGiftToStars_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ConvertGiftToStars_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ConvertGiftToStarsParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := ConvertGiftToStars(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ConvertGiftToStars_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ConvertGiftToStars_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ConvertGiftToStarsParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := ConvertGiftToStars(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UpgradeGift_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/upgradeGift")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UpgradeGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := UpgradeGift(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UpgradeGift_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UpgradeGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := UpgradeGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UpgradeGift_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UpgradeGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := UpgradeGift(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UpgradeGift_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UpgradeGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := UpgradeGift(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UpgradeGift_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UpgradeGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := UpgradeGift(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UpgradeGift_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UpgradeGift_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UpgradeGift(context.Background(), bot, &UpgradeGiftParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UpgradeGift_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UpgradeGift_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UpgradeGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := UpgradeGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UpgradeGift_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UpgradeGift_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UpgradeGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
}
_, err := UpgradeGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_TransferGift_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/transferGift")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
NewOwnerChatID: 42,
}
_, err := TransferGift(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_TransferGift_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
NewOwnerChatID: 42,
}
_, err := TransferGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_TransferGift_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
NewOwnerChatID: 42,
}
_, err := TransferGift(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_TransferGift_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
NewOwnerChatID: 42,
}
_, err := TransferGift(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_TransferGift_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
NewOwnerChatID: 42,
}
_, err := TransferGift(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_TransferGift_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_TransferGift_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := TransferGift(context.Background(), bot, &TransferGiftParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_TransferGift_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_TransferGift_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
NewOwnerChatID: 42,
}
_, err := TransferGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_TransferGift_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_TransferGift_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &TransferGiftParams{
BusinessConnectionID: "test_value",
OwnedGiftID: "test_value",
NewOwnerChatID: 42,
}
_, err := TransferGift(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_PostStory_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/postStory")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PostStoryParams{
BusinessConnectionID: "test_value",
Content: nil,
ActivePeriod: 42,
}
_, err := PostStory(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_PostStory_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PostStoryParams{
BusinessConnectionID: "test_value",
Content: nil,
ActivePeriod: 42,
}
_, err := PostStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_PostStory_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PostStoryParams{
BusinessConnectionID: "test_value",
Content: nil,
ActivePeriod: 42,
}
_, err := PostStory(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_PostStory_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PostStoryParams{
BusinessConnectionID: "test_value",
Content: nil,
ActivePeriod: 42,
}
_, err := PostStory(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_PostStory_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PostStoryParams{
BusinessConnectionID: "test_value",
Content: nil,
ActivePeriod: 42,
}
_, err := PostStory(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_PostStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_PostStory_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := PostStory(context.Background(), bot, &PostStoryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_PostStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_PostStory_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PostStoryParams{
BusinessConnectionID: "test_value",
Content: nil,
ActivePeriod: 42,
}
_, err := PostStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_PostStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_PostStory_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &PostStoryParams{
BusinessConnectionID: "test_value",
Content: nil,
ActivePeriod: 42,
}
_, err := PostStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RepostStory_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/repostStory")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RepostStoryParams{
BusinessConnectionID: "test_value",
FromChatID: 42,
FromStoryID: 42,
ActivePeriod: 42,
}
_, err := RepostStory(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_RepostStory_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RepostStoryParams{
BusinessConnectionID: "test_value",
FromChatID: 42,
FromStoryID: 42,
ActivePeriod: 42,
}
_, err := RepostStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RepostStory_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RepostStoryParams{
BusinessConnectionID: "test_value",
FromChatID: 42,
FromStoryID: 42,
ActivePeriod: 42,
}
_, err := RepostStory(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RepostStory_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RepostStoryParams{
BusinessConnectionID: "test_value",
FromChatID: 42,
FromStoryID: 42,
ActivePeriod: 42,
}
_, err := RepostStory(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RepostStory_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RepostStoryParams{
BusinessConnectionID: "test_value",
FromChatID: 42,
FromStoryID: 42,
ActivePeriod: 42,
}
_, err := RepostStory(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RepostStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RepostStory_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RepostStory(context.Background(), bot, &RepostStoryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RepostStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RepostStory_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RepostStoryParams{
BusinessConnectionID: "test_value",
FromChatID: 42,
FromStoryID: 42,
ActivePeriod: 42,
}
_, err := RepostStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RepostStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RepostStory_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RepostStoryParams{
BusinessConnectionID: "test_value",
FromChatID: 42,
FromStoryID: 42,
ActivePeriod: 42,
}
_, err := RepostStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditStory_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editStory")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
Content: nil,
}
_, err := EditStory(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditStory_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
Content: nil,
}
_, err := EditStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditStory_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
Content: nil,
}
_, err := EditStory(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditStory_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
Content: nil,
}
_, err := EditStory(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditStory_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
Content: nil,
}
_, err := EditStory(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditStory_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditStory(context.Background(), bot, &EditStoryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditStory_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
Content: nil,
}
_, err := EditStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditStory_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
Content: nil,
}
_, err := EditStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteStory_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteStory")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
}
_, err := DeleteStory(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteStory_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
}
_, err := DeleteStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteStory_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
}
_, err := DeleteStory(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteStory_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
}
_, err := DeleteStory(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteStory_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
}
_, err := DeleteStory(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteStory_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteStory_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteStory(context.Background(), bot, &DeleteStoryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteStory_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteStory_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
}
_, err := DeleteStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteStory_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteStory_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStoryParams{
BusinessConnectionID: "test_value",
StoryID: 42,
}
_, err := DeleteStory(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AnswerWebAppQuery_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/answerWebAppQuery")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerWebAppQueryParams{
WebAppQueryID: "test_value",
Result: nil,
}
_, err := AnswerWebAppQuery(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AnswerWebAppQuery_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerWebAppQueryParams{
WebAppQueryID: "test_value",
Result: nil,
}
_, err := AnswerWebAppQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AnswerWebAppQuery_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerWebAppQueryParams{
WebAppQueryID: "test_value",
Result: nil,
}
_, err := AnswerWebAppQuery(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AnswerWebAppQuery_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerWebAppQueryParams{
WebAppQueryID: "test_value",
Result: nil,
}
_, err := AnswerWebAppQuery(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AnswerWebAppQuery_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerWebAppQueryParams{
WebAppQueryID: "test_value",
Result: nil,
}
_, err := AnswerWebAppQuery(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AnswerWebAppQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerWebAppQuery_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AnswerWebAppQuery(context.Background(), bot, &AnswerWebAppQueryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AnswerWebAppQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerWebAppQuery_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerWebAppQueryParams{
WebAppQueryID: "test_value",
Result: nil,
}
_, err := AnswerWebAppQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AnswerWebAppQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerWebAppQuery_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerWebAppQueryParams{
WebAppQueryID: "test_value",
Result: nil,
}
_, err := AnswerWebAppQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SavePreparedInlineMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/savePreparedInlineMessage")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedInlineMessageParams{
UserID: 42,
Result: nil,
}
_, err := SavePreparedInlineMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SavePreparedInlineMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedInlineMessageParams{
UserID: 42,
Result: nil,
}
_, err := SavePreparedInlineMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SavePreparedInlineMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedInlineMessageParams{
UserID: 42,
Result: nil,
}
_, err := SavePreparedInlineMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SavePreparedInlineMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedInlineMessageParams{
UserID: 42,
Result: nil,
}
_, err := SavePreparedInlineMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SavePreparedInlineMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedInlineMessageParams{
UserID: 42,
Result: nil,
}
_, err := SavePreparedInlineMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SavePreparedInlineMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SavePreparedInlineMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SavePreparedInlineMessage(context.Background(), bot, &SavePreparedInlineMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SavePreparedInlineMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SavePreparedInlineMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedInlineMessageParams{
UserID: 42,
Result: nil,
}
_, err := SavePreparedInlineMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SavePreparedInlineMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SavePreparedInlineMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedInlineMessageParams{
UserID: 42,
Result: nil,
}
_, err := SavePreparedInlineMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SavePreparedKeyboardButton_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/savePreparedKeyboardButton")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedKeyboardButtonParams{
UserID: 42,
Button: KeyboardButton{},
}
_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SavePreparedKeyboardButton_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedKeyboardButtonParams{
UserID: 42,
Button: KeyboardButton{},
}
_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SavePreparedKeyboardButton_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedKeyboardButtonParams{
UserID: 42,
Button: KeyboardButton{},
}
_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SavePreparedKeyboardButton_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedKeyboardButtonParams{
UserID: 42,
Button: KeyboardButton{},
}
_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SavePreparedKeyboardButton_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedKeyboardButtonParams{
UserID: 42,
Button: KeyboardButton{},
}
_, err := SavePreparedKeyboardButton(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SavePreparedKeyboardButton_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SavePreparedKeyboardButton_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SavePreparedKeyboardButton(context.Background(), bot, &SavePreparedKeyboardButtonParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SavePreparedKeyboardButton_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SavePreparedKeyboardButton_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedKeyboardButtonParams{
UserID: 42,
Button: KeyboardButton{},
}
_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SavePreparedKeyboardButton_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SavePreparedKeyboardButton_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SavePreparedKeyboardButtonParams{
UserID: 42,
Button: KeyboardButton{},
}
_, err := SavePreparedKeyboardButton(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditMessageText_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editMessageText")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageTextParams{}
_, err := EditMessageText(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditMessageText_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageTextParams{}
_, err := EditMessageText(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditMessageText_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageTextParams{}
_, err := EditMessageText(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditMessageText_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageTextParams{}
_, err := EditMessageText(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditMessageText_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageTextParams{}
_, err := EditMessageText(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditMessageText_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageText_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditMessageText(context.Background(), bot, &EditMessageTextParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageText_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageText_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageTextParams{}
_, err := EditMessageText(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageText_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageText_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageTextParams{}
_, err := EditMessageText(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditMessageCaption_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editMessageCaption")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageCaptionParams{}
_, err := EditMessageCaption(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditMessageCaption_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageCaptionParams{}
_, err := EditMessageCaption(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditMessageCaption_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageCaptionParams{}
_, err := EditMessageCaption(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditMessageCaption_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageCaptionParams{}
_, err := EditMessageCaption(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditMessageCaption_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageCaptionParams{}
_, err := EditMessageCaption(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditMessageCaption_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageCaption_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditMessageCaption(context.Background(), bot, &EditMessageCaptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageCaption_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageCaption_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageCaptionParams{}
_, err := EditMessageCaption(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageCaption_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageCaption_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageCaptionParams{}
_, err := EditMessageCaption(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditMessageMedia_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editMessageMedia")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageMediaParams{
Media: nil,
}
_, err := EditMessageMedia(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditMessageMedia_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageMediaParams{
Media: nil,
}
_, err := EditMessageMedia(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditMessageMedia_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageMediaParams{
Media: nil,
}
_, err := EditMessageMedia(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditMessageMedia_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageMediaParams{
Media: nil,
}
_, err := EditMessageMedia(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditMessageMedia_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageMediaParams{
Media: nil,
}
_, err := EditMessageMedia(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditMessageMedia_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageMedia_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditMessageMedia(context.Background(), bot, &EditMessageMediaParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageMedia_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageMedia_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageMediaParams{
Media: nil,
}
_, err := EditMessageMedia(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageMedia_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageMedia_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageMediaParams{
Media: nil,
}
_, err := EditMessageMedia(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditMessageLiveLocation_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editMessageLiveLocation")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageLiveLocationParams{
Latitude: 1.0,
Longitude: 1.0,
}
_, err := EditMessageLiveLocation(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditMessageLiveLocation_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageLiveLocationParams{
Latitude: 1.0,
Longitude: 1.0,
}
_, err := EditMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditMessageLiveLocation_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageLiveLocationParams{
Latitude: 1.0,
Longitude: 1.0,
}
_, err := EditMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditMessageLiveLocation_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageLiveLocationParams{
Latitude: 1.0,
Longitude: 1.0,
}
_, err := EditMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditMessageLiveLocation_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageLiveLocationParams{
Latitude: 1.0,
Longitude: 1.0,
}
_, err := EditMessageLiveLocation(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditMessageLiveLocation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageLiveLocation_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditMessageLiveLocation(context.Background(), bot, &EditMessageLiveLocationParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageLiveLocation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageLiveLocation_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageLiveLocationParams{
Latitude: 1.0,
Longitude: 1.0,
}
_, err := EditMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageLiveLocation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageLiveLocation_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageLiveLocationParams{
Latitude: 1.0,
Longitude: 1.0,
}
_, err := EditMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_StopMessageLiveLocation_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/stopMessageLiveLocation")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopMessageLiveLocationParams{}
_, err := StopMessageLiveLocation(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_StopMessageLiveLocation_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopMessageLiveLocationParams{}
_, err := StopMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_StopMessageLiveLocation_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopMessageLiveLocationParams{}
_, err := StopMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_StopMessageLiveLocation_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopMessageLiveLocationParams{}
_, err := StopMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_StopMessageLiveLocation_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopMessageLiveLocationParams{}
_, err := StopMessageLiveLocation(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_StopMessageLiveLocation_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_StopMessageLiveLocation_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := StopMessageLiveLocation(context.Background(), bot, &StopMessageLiveLocationParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_StopMessageLiveLocation_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_StopMessageLiveLocation_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopMessageLiveLocationParams{}
_, err := StopMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_StopMessageLiveLocation_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_StopMessageLiveLocation_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopMessageLiveLocationParams{}
_, err := StopMessageLiveLocation(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditMessageChecklist_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editMessageChecklist")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
MessageID: 42,
Checklist: InputChecklist{},
}
_, err := EditMessageChecklist(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditMessageChecklist_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
MessageID: 42,
Checklist: InputChecklist{},
}
_, err := EditMessageChecklist(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditMessageChecklist_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
MessageID: 42,
Checklist: InputChecklist{},
}
_, err := EditMessageChecklist(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditMessageChecklist_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
MessageID: 42,
Checklist: InputChecklist{},
}
_, err := EditMessageChecklist(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditMessageChecklist_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
MessageID: 42,
Checklist: InputChecklist{},
}
_, err := EditMessageChecklist(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditMessageChecklist_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageChecklist_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditMessageChecklist(context.Background(), bot, &EditMessageChecklistParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageChecklist_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageChecklist_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
MessageID: 42,
Checklist: InputChecklist{},
}
_, err := EditMessageChecklist(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageChecklist_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageChecklist_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageChecklistParams{
BusinessConnectionID: "test_value",
ChatID: ChatIDFromInt(123),
MessageID: 42,
Checklist: InputChecklist{},
}
_, err := EditMessageChecklist(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditMessageReplyMarkup_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editMessageReplyMarkup")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageReplyMarkupParams{}
_, err := EditMessageReplyMarkup(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditMessageReplyMarkup_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageReplyMarkupParams{}
_, err := EditMessageReplyMarkup(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditMessageReplyMarkup_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageReplyMarkupParams{}
_, err := EditMessageReplyMarkup(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditMessageReplyMarkup_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageReplyMarkupParams{}
_, err := EditMessageReplyMarkup(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditMessageReplyMarkup_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageReplyMarkupParams{}
_, err := EditMessageReplyMarkup(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditMessageReplyMarkup_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditMessageReplyMarkup_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditMessageReplyMarkup(context.Background(), bot, &EditMessageReplyMarkupParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageReplyMarkup_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditMessageReplyMarkup_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageReplyMarkupParams{}
_, err := EditMessageReplyMarkup(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditMessageReplyMarkup_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditMessageReplyMarkup_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditMessageReplyMarkupParams{}
_, err := EditMessageReplyMarkup(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_StopPoll_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/stopPoll")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopPollParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := StopPoll(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_StopPoll_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopPollParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := StopPoll(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_StopPoll_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopPollParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := StopPoll(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_StopPoll_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopPollParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := StopPoll(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_StopPoll_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopPollParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := StopPoll(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_StopPoll_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_StopPoll_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := StopPoll(context.Background(), bot, &StopPollParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_StopPoll_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_StopPoll_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopPollParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := StopPoll(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_StopPoll_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_StopPoll_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &StopPollParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := StopPoll(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ApproveSuggestedPost_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/approveSuggestedPost")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := ApproveSuggestedPost(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ApproveSuggestedPost_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := ApproveSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ApproveSuggestedPost_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := ApproveSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ApproveSuggestedPost_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := ApproveSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ApproveSuggestedPost_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := ApproveSuggestedPost(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ApproveSuggestedPost_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ApproveSuggestedPost_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ApproveSuggestedPost(context.Background(), bot, &ApproveSuggestedPostParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ApproveSuggestedPost_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ApproveSuggestedPost_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := ApproveSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ApproveSuggestedPost_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ApproveSuggestedPost_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ApproveSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := ApproveSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeclineSuggestedPost_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/declineSuggestedPost")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := DeclineSuggestedPost(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeclineSuggestedPost_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := DeclineSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeclineSuggestedPost_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := DeclineSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeclineSuggestedPost_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := DeclineSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeclineSuggestedPost_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := DeclineSuggestedPost(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeclineSuggestedPost_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeclineSuggestedPost_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeclineSuggestedPost(context.Background(), bot, &DeclineSuggestedPostParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeclineSuggestedPost_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeclineSuggestedPost_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := DeclineSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeclineSuggestedPost_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeclineSuggestedPost_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeclineSuggestedPostParams{
ChatID: 42,
MessageID: 42,
}
_, err := DeclineSuggestedPost(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteMessage")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteMessage(context.Background(), bot, &DeleteMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteMessages_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteMessages")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessagesParams{
ChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := DeleteMessages(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteMessages_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessagesParams{
ChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := DeleteMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteMessages_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessagesParams{
ChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := DeleteMessages(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteMessages_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessagesParams{
ChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := DeleteMessages(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteMessages_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessagesParams{
ChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := DeleteMessages(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteMessages_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMessages_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteMessages(context.Background(), bot, &DeleteMessagesParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMessages_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMessages_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessagesParams{
ChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := DeleteMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMessages_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMessages_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessagesParams{
ChatID: ChatIDFromInt(123),
MessageIds: nil,
}
_, err := DeleteMessages(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteMessageReaction_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteMessageReaction")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessageReaction(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteMessageReaction_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteMessageReaction_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteMessageReaction_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteMessageReaction_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessageReaction(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteMessageReaction_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteMessageReaction_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteMessageReaction(context.Background(), bot, &DeleteMessageReactionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMessageReaction_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteMessageReaction_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteMessageReaction_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteMessageReaction_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteMessageReactionParams{
ChatID: ChatIDFromInt(123),
MessageID: 42,
}
_, err := DeleteMessageReaction(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteAllMessageReactions_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteAllMessageReactions")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteAllMessageReactionsParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteAllMessageReactions(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteAllMessageReactions_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteAllMessageReactionsParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteAllMessageReactions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteAllMessageReactions_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteAllMessageReactionsParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteAllMessageReactions(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteAllMessageReactions_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteAllMessageReactionsParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteAllMessageReactions(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteAllMessageReactions_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteAllMessageReactionsParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteAllMessageReactions(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteAllMessageReactions_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteAllMessageReactions_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteAllMessageReactions(context.Background(), bot, &DeleteAllMessageReactionsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteAllMessageReactions_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteAllMessageReactions_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteAllMessageReactionsParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteAllMessageReactions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteAllMessageReactions_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteAllMessageReactions_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteAllMessageReactionsParams{
ChatID: ChatIDFromInt(123),
}
_, err := DeleteAllMessageReactions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendSticker_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendSticker")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendStickerParams{
ChatID: ChatIDFromInt(123),
Sticker: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendSticker(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendSticker_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendStickerParams{
ChatID: ChatIDFromInt(123),
Sticker: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendSticker(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendSticker_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendStickerParams{
ChatID: ChatIDFromInt(123),
Sticker: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendSticker(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendSticker_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendStickerParams{
ChatID: ChatIDFromInt(123),
Sticker: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendSticker(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendSticker_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendStickerParams{
ChatID: ChatIDFromInt(123),
Sticker: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendSticker(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendSticker_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendSticker_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendSticker(context.Background(), bot, &SendStickerParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendSticker_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendSticker_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendStickerParams{
ChatID: ChatIDFromInt(123),
Sticker: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendSticker(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendSticker_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendSticker_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendStickerParams{
ChatID: ChatIDFromInt(123),
Sticker: &InputFile{PathOrID: "file_id_test"},
}
_, err := SendSticker(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetStickerSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getStickerSet")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStickerSetParams{
Name: "test_value",
}
_, err := GetStickerSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetStickerSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStickerSetParams{
Name: "test_value",
}
_, err := GetStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetStickerSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStickerSetParams{
Name: "test_value",
}
_, err := GetStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetStickerSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStickerSetParams{
Name: "test_value",
}
_, err := GetStickerSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetStickerSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStickerSetParams{
Name: "test_value",
}
_, err := GetStickerSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetStickerSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetStickerSet(context.Background(), bot, &GetStickerSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetStickerSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStickerSetParams{
Name: "test_value",
}
_, err := GetStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetStickerSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStickerSetParams{
Name: "test_value",
}
_, err := GetStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetCustomEmojiStickers_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getCustomEmojiStickers")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetCustomEmojiStickersParams{
CustomEmojiIds: nil,
}
_, err := GetCustomEmojiStickers(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetCustomEmojiStickers_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetCustomEmojiStickersParams{
CustomEmojiIds: nil,
}
_, err := GetCustomEmojiStickers(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetCustomEmojiStickers_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetCustomEmojiStickersParams{
CustomEmojiIds: nil,
}
_, err := GetCustomEmojiStickers(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetCustomEmojiStickers_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetCustomEmojiStickersParams{
CustomEmojiIds: nil,
}
_, err := GetCustomEmojiStickers(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetCustomEmojiStickers_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetCustomEmojiStickersParams{
CustomEmojiIds: nil,
}
_, err := GetCustomEmojiStickers(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetCustomEmojiStickers_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetCustomEmojiStickers_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetCustomEmojiStickers(context.Background(), bot, &GetCustomEmojiStickersParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetCustomEmojiStickers_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetCustomEmojiStickers_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetCustomEmojiStickersParams{
CustomEmojiIds: nil,
}
_, err := GetCustomEmojiStickers(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetCustomEmojiStickers_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetCustomEmojiStickers_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetCustomEmojiStickersParams{
CustomEmojiIds: nil,
}
_, err := GetCustomEmojiStickers(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_UploadStickerFile_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/uploadStickerFile")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UploadStickerFileParams{
UserID: 42,
Sticker: &InputFile{PathOrID: "file_id_test"},
StickerFormat: InputStickerFormatStatic,
}
_, err := UploadStickerFile(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_UploadStickerFile_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UploadStickerFileParams{
UserID: 42,
Sticker: &InputFile{PathOrID: "file_id_test"},
StickerFormat: InputStickerFormatStatic,
}
_, err := UploadStickerFile(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_UploadStickerFile_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UploadStickerFileParams{
UserID: 42,
Sticker: &InputFile{PathOrID: "file_id_test"},
StickerFormat: InputStickerFormatStatic,
}
_, err := UploadStickerFile(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_UploadStickerFile_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UploadStickerFileParams{
UserID: 42,
Sticker: &InputFile{PathOrID: "file_id_test"},
StickerFormat: InputStickerFormatStatic,
}
_, err := UploadStickerFile(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_UploadStickerFile_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UploadStickerFileParams{
UserID: 42,
Sticker: &InputFile{PathOrID: "file_id_test"},
StickerFormat: InputStickerFormatStatic,
}
_, err := UploadStickerFile(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_UploadStickerFile_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_UploadStickerFile_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := UploadStickerFile(context.Background(), bot, &UploadStickerFileParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_UploadStickerFile_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_UploadStickerFile_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UploadStickerFileParams{
UserID: 42,
Sticker: &InputFile{PathOrID: "file_id_test"},
StickerFormat: InputStickerFormatStatic,
}
_, err := UploadStickerFile(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_UploadStickerFile_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_UploadStickerFile_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &UploadStickerFileParams{
UserID: 42,
Sticker: &InputFile{PathOrID: "file_id_test"},
StickerFormat: InputStickerFormatStatic,
}
_, err := UploadStickerFile(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CreateNewStickerSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/createNewStickerSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateNewStickerSetParams{
UserID: 42,
Name: "test_value",
Title: "test_value",
Stickers: nil,
}
_, err := CreateNewStickerSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CreateNewStickerSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateNewStickerSetParams{
UserID: 42,
Name: "test_value",
Title: "test_value",
Stickers: nil,
}
_, err := CreateNewStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CreateNewStickerSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateNewStickerSetParams{
UserID: 42,
Name: "test_value",
Title: "test_value",
Stickers: nil,
}
_, err := CreateNewStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CreateNewStickerSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateNewStickerSetParams{
UserID: 42,
Name: "test_value",
Title: "test_value",
Stickers: nil,
}
_, err := CreateNewStickerSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CreateNewStickerSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateNewStickerSetParams{
UserID: 42,
Name: "test_value",
Title: "test_value",
Stickers: nil,
}
_, err := CreateNewStickerSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CreateNewStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateNewStickerSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CreateNewStickerSet(context.Background(), bot, &CreateNewStickerSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CreateNewStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateNewStickerSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateNewStickerSetParams{
UserID: 42,
Name: "test_value",
Title: "test_value",
Stickers: nil,
}
_, err := CreateNewStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CreateNewStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateNewStickerSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateNewStickerSetParams{
UserID: 42,
Name: "test_value",
Title: "test_value",
Stickers: nil,
}
_, err := CreateNewStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AddStickerToSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/addStickerToSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AddStickerToSetParams{
UserID: 42,
Name: "test_value",
Sticker: InputSticker{},
}
_, err := AddStickerToSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AddStickerToSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AddStickerToSetParams{
UserID: 42,
Name: "test_value",
Sticker: InputSticker{},
}
_, err := AddStickerToSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AddStickerToSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AddStickerToSetParams{
UserID: 42,
Name: "test_value",
Sticker: InputSticker{},
}
_, err := AddStickerToSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AddStickerToSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AddStickerToSetParams{
UserID: 42,
Name: "test_value",
Sticker: InputSticker{},
}
_, err := AddStickerToSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AddStickerToSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AddStickerToSetParams{
UserID: 42,
Name: "test_value",
Sticker: InputSticker{},
}
_, err := AddStickerToSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AddStickerToSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AddStickerToSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AddStickerToSet(context.Background(), bot, &AddStickerToSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AddStickerToSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AddStickerToSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AddStickerToSetParams{
UserID: 42,
Name: "test_value",
Sticker: InputSticker{},
}
_, err := AddStickerToSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AddStickerToSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AddStickerToSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AddStickerToSetParams{
UserID: 42,
Name: "test_value",
Sticker: InputSticker{},
}
_, err := AddStickerToSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetStickerPositionInSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setStickerPositionInSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerPositionInSetParams{
Sticker: "test_value",
Position: 42,
}
_, err := SetStickerPositionInSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetStickerPositionInSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerPositionInSetParams{
Sticker: "test_value",
Position: 42,
}
_, err := SetStickerPositionInSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetStickerPositionInSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerPositionInSetParams{
Sticker: "test_value",
Position: 42,
}
_, err := SetStickerPositionInSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetStickerPositionInSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerPositionInSetParams{
Sticker: "test_value",
Position: 42,
}
_, err := SetStickerPositionInSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetStickerPositionInSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerPositionInSetParams{
Sticker: "test_value",
Position: 42,
}
_, err := SetStickerPositionInSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetStickerPositionInSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerPositionInSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetStickerPositionInSet(context.Background(), bot, &SetStickerPositionInSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerPositionInSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerPositionInSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerPositionInSetParams{
Sticker: "test_value",
Position: 42,
}
_, err := SetStickerPositionInSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerPositionInSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerPositionInSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerPositionInSetParams{
Sticker: "test_value",
Position: 42,
}
_, err := SetStickerPositionInSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteStickerFromSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteStickerFromSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerFromSetParams{
Sticker: "test_value",
}
_, err := DeleteStickerFromSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteStickerFromSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerFromSetParams{
Sticker: "test_value",
}
_, err := DeleteStickerFromSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteStickerFromSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerFromSetParams{
Sticker: "test_value",
}
_, err := DeleteStickerFromSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteStickerFromSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerFromSetParams{
Sticker: "test_value",
}
_, err := DeleteStickerFromSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteStickerFromSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerFromSetParams{
Sticker: "test_value",
}
_, err := DeleteStickerFromSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteStickerFromSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteStickerFromSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteStickerFromSet(context.Background(), bot, &DeleteStickerFromSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteStickerFromSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteStickerFromSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerFromSetParams{
Sticker: "test_value",
}
_, err := DeleteStickerFromSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteStickerFromSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteStickerFromSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerFromSetParams{
Sticker: "test_value",
}
_, err := DeleteStickerFromSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_ReplaceStickerInSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/replaceStickerInSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceStickerInSetParams{
UserID: 42,
Name: "test_value",
OldSticker: "test_value",
Sticker: InputSticker{},
}
_, err := ReplaceStickerInSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_ReplaceStickerInSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceStickerInSetParams{
UserID: 42,
Name: "test_value",
OldSticker: "test_value",
Sticker: InputSticker{},
}
_, err := ReplaceStickerInSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_ReplaceStickerInSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceStickerInSetParams{
UserID: 42,
Name: "test_value",
OldSticker: "test_value",
Sticker: InputSticker{},
}
_, err := ReplaceStickerInSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_ReplaceStickerInSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceStickerInSetParams{
UserID: 42,
Name: "test_value",
OldSticker: "test_value",
Sticker: InputSticker{},
}
_, err := ReplaceStickerInSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_ReplaceStickerInSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceStickerInSetParams{
UserID: 42,
Name: "test_value",
OldSticker: "test_value",
Sticker: InputSticker{},
}
_, err := ReplaceStickerInSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_ReplaceStickerInSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_ReplaceStickerInSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := ReplaceStickerInSet(context.Background(), bot, &ReplaceStickerInSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_ReplaceStickerInSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_ReplaceStickerInSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceStickerInSetParams{
UserID: 42,
Name: "test_value",
OldSticker: "test_value",
Sticker: InputSticker{},
}
_, err := ReplaceStickerInSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_ReplaceStickerInSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_ReplaceStickerInSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &ReplaceStickerInSetParams{
UserID: 42,
Name: "test_value",
OldSticker: "test_value",
Sticker: InputSticker{},
}
_, err := ReplaceStickerInSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetStickerEmojiList_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setStickerEmojiList")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerEmojiListParams{
Sticker: "test_value",
EmojiList: nil,
}
_, err := SetStickerEmojiList(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetStickerEmojiList_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerEmojiListParams{
Sticker: "test_value",
EmojiList: nil,
}
_, err := SetStickerEmojiList(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetStickerEmojiList_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerEmojiListParams{
Sticker: "test_value",
EmojiList: nil,
}
_, err := SetStickerEmojiList(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetStickerEmojiList_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerEmojiListParams{
Sticker: "test_value",
EmojiList: nil,
}
_, err := SetStickerEmojiList(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetStickerEmojiList_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerEmojiListParams{
Sticker: "test_value",
EmojiList: nil,
}
_, err := SetStickerEmojiList(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetStickerEmojiList_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerEmojiList_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetStickerEmojiList(context.Background(), bot, &SetStickerEmojiListParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerEmojiList_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerEmojiList_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerEmojiListParams{
Sticker: "test_value",
EmojiList: nil,
}
_, err := SetStickerEmojiList(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerEmojiList_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerEmojiList_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerEmojiListParams{
Sticker: "test_value",
EmojiList: nil,
}
_, err := SetStickerEmojiList(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetStickerKeywords_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setStickerKeywords")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerKeywordsParams{
Sticker: "test_value",
}
_, err := SetStickerKeywords(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetStickerKeywords_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerKeywordsParams{
Sticker: "test_value",
}
_, err := SetStickerKeywords(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetStickerKeywords_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerKeywordsParams{
Sticker: "test_value",
}
_, err := SetStickerKeywords(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetStickerKeywords_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerKeywordsParams{
Sticker: "test_value",
}
_, err := SetStickerKeywords(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetStickerKeywords_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerKeywordsParams{
Sticker: "test_value",
}
_, err := SetStickerKeywords(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetStickerKeywords_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerKeywords_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetStickerKeywords(context.Background(), bot, &SetStickerKeywordsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerKeywords_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerKeywords_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerKeywordsParams{
Sticker: "test_value",
}
_, err := SetStickerKeywords(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerKeywords_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerKeywords_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerKeywordsParams{
Sticker: "test_value",
}
_, err := SetStickerKeywords(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetStickerMaskPosition_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setStickerMaskPosition")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerMaskPositionParams{
Sticker: "test_value",
}
_, err := SetStickerMaskPosition(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetStickerMaskPosition_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerMaskPositionParams{
Sticker: "test_value",
}
_, err := SetStickerMaskPosition(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetStickerMaskPosition_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerMaskPositionParams{
Sticker: "test_value",
}
_, err := SetStickerMaskPosition(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetStickerMaskPosition_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerMaskPositionParams{
Sticker: "test_value",
}
_, err := SetStickerMaskPosition(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetStickerMaskPosition_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerMaskPositionParams{
Sticker: "test_value",
}
_, err := SetStickerMaskPosition(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetStickerMaskPosition_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerMaskPosition_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetStickerMaskPosition(context.Background(), bot, &SetStickerMaskPositionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerMaskPosition_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerMaskPosition_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerMaskPositionParams{
Sticker: "test_value",
}
_, err := SetStickerMaskPosition(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerMaskPosition_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerMaskPosition_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerMaskPositionParams{
Sticker: "test_value",
}
_, err := SetStickerMaskPosition(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetStickerSetTitle_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setStickerSetTitle")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetTitleParams{
Name: "test_value",
Title: "test_value",
}
_, err := SetStickerSetTitle(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetStickerSetTitle_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetTitleParams{
Name: "test_value",
Title: "test_value",
}
_, err := SetStickerSetTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetStickerSetTitle_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetTitleParams{
Name: "test_value",
Title: "test_value",
}
_, err := SetStickerSetTitle(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetStickerSetTitle_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetTitleParams{
Name: "test_value",
Title: "test_value",
}
_, err := SetStickerSetTitle(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetStickerSetTitle_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetTitleParams{
Name: "test_value",
Title: "test_value",
}
_, err := SetStickerSetTitle(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetStickerSetTitle_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerSetTitle_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetStickerSetTitle(context.Background(), bot, &SetStickerSetTitleParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerSetTitle_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerSetTitle_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetTitleParams{
Name: "test_value",
Title: "test_value",
}
_, err := SetStickerSetTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerSetTitle_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerSetTitle_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetTitleParams{
Name: "test_value",
Title: "test_value",
}
_, err := SetStickerSetTitle(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetStickerSetThumbnail_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setStickerSetThumbnail")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetThumbnailParams{
Name: "test_value",
UserID: 42,
Format: InputStickerFormatStatic,
}
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetStickerSetThumbnail_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetThumbnailParams{
Name: "test_value",
UserID: 42,
Format: InputStickerFormatStatic,
}
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetStickerSetThumbnail_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetThumbnailParams{
Name: "test_value",
UserID: 42,
Format: InputStickerFormatStatic,
}
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetStickerSetThumbnail_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetThumbnailParams{
Name: "test_value",
UserID: 42,
Format: InputStickerFormatStatic,
}
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetStickerSetThumbnail_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetThumbnailParams{
Name: "test_value",
UserID: 42,
Format: InputStickerFormatStatic,
}
_, err := SetStickerSetThumbnail(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetStickerSetThumbnail_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetStickerSetThumbnail_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetStickerSetThumbnail(context.Background(), bot, &SetStickerSetThumbnailParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerSetThumbnail_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetStickerSetThumbnail_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetThumbnailParams{
Name: "test_value",
UserID: 42,
Format: InputStickerFormatStatic,
}
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetStickerSetThumbnail_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetStickerSetThumbnail_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetStickerSetThumbnailParams{
Name: "test_value",
UserID: 42,
Format: InputStickerFormatStatic,
}
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetCustomEmojiStickerSetThumbnail_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setCustomEmojiStickerSetThumbnail")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetCustomEmojiStickerSetThumbnailParams{
Name: "test_value",
}
_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetCustomEmojiStickerSetThumbnail_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetCustomEmojiStickerSetThumbnailParams{
Name: "test_value",
}
_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetCustomEmojiStickerSetThumbnail_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetCustomEmojiStickerSetThumbnailParams{
Name: "test_value",
}
_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetCustomEmojiStickerSetThumbnail_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetCustomEmojiStickerSetThumbnailParams{
Name: "test_value",
}
_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetCustomEmojiStickerSetThumbnail_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetCustomEmojiStickerSetThumbnailParams{
Name: "test_value",
}
_, err := SetCustomEmojiStickerSetThumbnail(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetCustomEmojiStickerSetThumbnail_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetCustomEmojiStickerSetThumbnail_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, &SetCustomEmojiStickerSetThumbnailParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetCustomEmojiStickerSetThumbnail_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetCustomEmojiStickerSetThumbnail_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetCustomEmojiStickerSetThumbnailParams{
Name: "test_value",
}
_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetCustomEmojiStickerSetThumbnail_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetCustomEmojiStickerSetThumbnail_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetCustomEmojiStickerSetThumbnailParams{
Name: "test_value",
}
_, err := SetCustomEmojiStickerSetThumbnail(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_DeleteStickerSet_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/deleteStickerSet")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerSetParams{
Name: "test_value",
}
_, err := DeleteStickerSet(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_DeleteStickerSet_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerSetParams{
Name: "test_value",
}
_, err := DeleteStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_DeleteStickerSet_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerSetParams{
Name: "test_value",
}
_, err := DeleteStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_DeleteStickerSet_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerSetParams{
Name: "test_value",
}
_, err := DeleteStickerSet(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_DeleteStickerSet_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerSetParams{
Name: "test_value",
}
_, err := DeleteStickerSet(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_DeleteStickerSet_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_DeleteStickerSet_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := DeleteStickerSet(context.Background(), bot, &DeleteStickerSetParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_DeleteStickerSet_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_DeleteStickerSet_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerSetParams{
Name: "test_value",
}
_, err := DeleteStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_DeleteStickerSet_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_DeleteStickerSet_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &DeleteStickerSetParams{
Name: "test_value",
}
_, err := DeleteStickerSet(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendRichMessage_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendRichMessage")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageParams{
ChatID: ChatIDFromInt(123),
RichMessage: InputRichMessage{},
}
_, err := SendRichMessage(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendRichMessage_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageParams{
ChatID: ChatIDFromInt(123),
RichMessage: InputRichMessage{},
}
_, err := SendRichMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendRichMessage_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageParams{
ChatID: ChatIDFromInt(123),
RichMessage: InputRichMessage{},
}
_, err := SendRichMessage(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendRichMessage_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageParams{
ChatID: ChatIDFromInt(123),
RichMessage: InputRichMessage{},
}
_, err := SendRichMessage(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendRichMessage_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageParams{
ChatID: ChatIDFromInt(123),
RichMessage: InputRichMessage{},
}
_, err := SendRichMessage(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendRichMessage_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendRichMessage_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendRichMessage(context.Background(), bot, &SendRichMessageParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendRichMessage_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendRichMessage_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageParams{
ChatID: ChatIDFromInt(123),
RichMessage: InputRichMessage{},
}
_, err := SendRichMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendRichMessage_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendRichMessage_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageParams{
ChatID: ChatIDFromInt(123),
RichMessage: InputRichMessage{},
}
_, err := SendRichMessage(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendRichMessageDraft_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendRichMessageDraft")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageDraftParams{
ChatID: 42,
DraftID: 42,
RichMessage: InputRichMessage{},
}
_, err := SendRichMessageDraft(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendRichMessageDraft_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageDraftParams{
ChatID: 42,
DraftID: 42,
RichMessage: InputRichMessage{},
}
_, err := SendRichMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendRichMessageDraft_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageDraftParams{
ChatID: 42,
DraftID: 42,
RichMessage: InputRichMessage{},
}
_, err := SendRichMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendRichMessageDraft_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageDraftParams{
ChatID: 42,
DraftID: 42,
RichMessage: InputRichMessage{},
}
_, err := SendRichMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendRichMessageDraft_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageDraftParams{
ChatID: 42,
DraftID: 42,
RichMessage: InputRichMessage{},
}
_, err := SendRichMessageDraft(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendRichMessageDraft_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendRichMessageDraft_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendRichMessageDraft(context.Background(), bot, &SendRichMessageDraftParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendRichMessageDraft_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendRichMessageDraft_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageDraftParams{
ChatID: 42,
DraftID: 42,
RichMessage: InputRichMessage{},
}
_, err := SendRichMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendRichMessageDraft_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendRichMessageDraft_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendRichMessageDraftParams{
ChatID: 42,
DraftID: 42,
RichMessage: InputRichMessage{},
}
_, err := SendRichMessageDraft(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AnswerInlineQuery_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/answerInlineQuery")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerInlineQueryParams{
InlineQueryID: "test_value",
Results: nil,
}
_, err := AnswerInlineQuery(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AnswerInlineQuery_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerInlineQueryParams{
InlineQueryID: "test_value",
Results: nil,
}
_, err := AnswerInlineQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AnswerInlineQuery_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerInlineQueryParams{
InlineQueryID: "test_value",
Results: nil,
}
_, err := AnswerInlineQuery(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AnswerInlineQuery_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerInlineQueryParams{
InlineQueryID: "test_value",
Results: nil,
}
_, err := AnswerInlineQuery(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AnswerInlineQuery_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerInlineQueryParams{
InlineQueryID: "test_value",
Results: nil,
}
_, err := AnswerInlineQuery(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AnswerInlineQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerInlineQuery_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AnswerInlineQuery(context.Background(), bot, &AnswerInlineQueryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AnswerInlineQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerInlineQuery_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerInlineQueryParams{
InlineQueryID: "test_value",
Results: nil,
}
_, err := AnswerInlineQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AnswerInlineQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerInlineQuery_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerInlineQueryParams{
InlineQueryID: "test_value",
Results: nil,
}
_, err := AnswerInlineQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendInvoice_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendInvoice")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendInvoiceParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := SendInvoice(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendInvoice_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendInvoiceParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := SendInvoice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendInvoice_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendInvoiceParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := SendInvoice(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendInvoice_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendInvoiceParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := SendInvoice(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendInvoice_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendInvoiceParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := SendInvoice(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendInvoice_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendInvoice_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendInvoice(context.Background(), bot, &SendInvoiceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendInvoice_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendInvoice_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendInvoiceParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := SendInvoice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendInvoice_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendInvoice_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendInvoiceParams{
ChatID: ChatIDFromInt(123),
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := SendInvoice(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_CreateInvoiceLink_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/createInvoiceLink")
})).Return(genTestResp(200, `{"ok":true,"result":""}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateInvoiceLinkParams{
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := CreateInvoiceLink(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_CreateInvoiceLink_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateInvoiceLinkParams{
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := CreateInvoiceLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_CreateInvoiceLink_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateInvoiceLinkParams{
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := CreateInvoiceLink(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_CreateInvoiceLink_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateInvoiceLinkParams{
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := CreateInvoiceLink(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_CreateInvoiceLink_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateInvoiceLinkParams{
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := CreateInvoiceLink(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_CreateInvoiceLink_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_CreateInvoiceLink_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := CreateInvoiceLink(context.Background(), bot, &CreateInvoiceLinkParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_CreateInvoiceLink_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_CreateInvoiceLink_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateInvoiceLinkParams{
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := CreateInvoiceLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_CreateInvoiceLink_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_CreateInvoiceLink_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &CreateInvoiceLinkParams{
Title: "test_value",
Description: "test_value",
Payload: "test_value",
Currency: "test_value",
Prices: nil,
}
_, err := CreateInvoiceLink(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AnswerShippingQuery_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/answerShippingQuery")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerShippingQueryParams{
ShippingQueryID: "test_value",
Ok: true,
}
_, err := AnswerShippingQuery(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AnswerShippingQuery_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerShippingQueryParams{
ShippingQueryID: "test_value",
Ok: true,
}
_, err := AnswerShippingQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AnswerShippingQuery_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerShippingQueryParams{
ShippingQueryID: "test_value",
Ok: true,
}
_, err := AnswerShippingQuery(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AnswerShippingQuery_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerShippingQueryParams{
ShippingQueryID: "test_value",
Ok: true,
}
_, err := AnswerShippingQuery(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AnswerShippingQuery_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerShippingQueryParams{
ShippingQueryID: "test_value",
Ok: true,
}
_, err := AnswerShippingQuery(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AnswerShippingQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerShippingQuery_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AnswerShippingQuery(context.Background(), bot, &AnswerShippingQueryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AnswerShippingQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerShippingQuery_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerShippingQueryParams{
ShippingQueryID: "test_value",
Ok: true,
}
_, err := AnswerShippingQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AnswerShippingQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerShippingQuery_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerShippingQueryParams{
ShippingQueryID: "test_value",
Ok: true,
}
_, err := AnswerShippingQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_AnswerPreCheckoutQuery_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/answerPreCheckoutQuery")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerPreCheckoutQueryParams{
PreCheckoutQueryID: "test_value",
Ok: true,
}
_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_AnswerPreCheckoutQuery_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerPreCheckoutQueryParams{
PreCheckoutQueryID: "test_value",
Ok: true,
}
_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_AnswerPreCheckoutQuery_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerPreCheckoutQueryParams{
PreCheckoutQueryID: "test_value",
Ok: true,
}
_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_AnswerPreCheckoutQuery_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerPreCheckoutQueryParams{
PreCheckoutQueryID: "test_value",
Ok: true,
}
_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_AnswerPreCheckoutQuery_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerPreCheckoutQueryParams{
PreCheckoutQueryID: "test_value",
Ok: true,
}
_, err := AnswerPreCheckoutQuery(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_AnswerPreCheckoutQuery_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_AnswerPreCheckoutQuery_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := AnswerPreCheckoutQuery(context.Background(), bot, &AnswerPreCheckoutQueryParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_AnswerPreCheckoutQuery_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_AnswerPreCheckoutQuery_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerPreCheckoutQueryParams{
PreCheckoutQueryID: "test_value",
Ok: true,
}
_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_AnswerPreCheckoutQuery_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_AnswerPreCheckoutQuery_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &AnswerPreCheckoutQueryParams{
PreCheckoutQueryID: "test_value",
Ok: true,
}
_, err := AnswerPreCheckoutQuery(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetMyStarBalance_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getMyStarBalance")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
require.NoError(t, err)
}
func Test_GetMyStarBalance_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetMyStarBalance_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetMyStarBalance_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetMyStarBalance_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMyStarBalance(ctx, bot, &GetMyStarBalanceParams{})
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetMyStarBalance_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetMyStarBalance_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetMyStarBalance_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetMyStarBalance_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetMyStarBalance_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetMyStarBalance_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
_, err := GetMyStarBalance(context.Background(), bot, &GetMyStarBalanceParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetStarTransactions_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getStarTransactions")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStarTransactionsParams{}
_, err := GetStarTransactions(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetStarTransactions_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStarTransactionsParams{}
_, err := GetStarTransactions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetStarTransactions_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStarTransactionsParams{}
_, err := GetStarTransactions(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetStarTransactions_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStarTransactionsParams{}
_, err := GetStarTransactions(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetStarTransactions_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStarTransactionsParams{}
_, err := GetStarTransactions(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetStarTransactions_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetStarTransactions_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetStarTransactions(context.Background(), bot, &GetStarTransactionsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetStarTransactions_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetStarTransactions_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStarTransactionsParams{}
_, err := GetStarTransactions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetStarTransactions_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetStarTransactions_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetStarTransactionsParams{}
_, err := GetStarTransactions(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_RefundStarPayment_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/refundStarPayment")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RefundStarPaymentParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
}
_, err := RefundStarPayment(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_RefundStarPayment_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RefundStarPaymentParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
}
_, err := RefundStarPayment(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_RefundStarPayment_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RefundStarPaymentParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
}
_, err := RefundStarPayment(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_RefundStarPayment_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RefundStarPaymentParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
}
_, err := RefundStarPayment(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_RefundStarPayment_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RefundStarPaymentParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
}
_, err := RefundStarPayment(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_RefundStarPayment_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_RefundStarPayment_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := RefundStarPayment(context.Background(), bot, &RefundStarPaymentParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_RefundStarPayment_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_RefundStarPayment_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RefundStarPaymentParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
}
_, err := RefundStarPayment(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_RefundStarPayment_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_RefundStarPayment_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &RefundStarPaymentParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
}
_, err := RefundStarPayment(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_EditUserStarSubscription_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/editUserStarSubscription")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditUserStarSubscriptionParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
IsCanceled: true,
}
_, err := EditUserStarSubscription(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_EditUserStarSubscription_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditUserStarSubscriptionParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
IsCanceled: true,
}
_, err := EditUserStarSubscription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_EditUserStarSubscription_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditUserStarSubscriptionParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
IsCanceled: true,
}
_, err := EditUserStarSubscription(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_EditUserStarSubscription_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditUserStarSubscriptionParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
IsCanceled: true,
}
_, err := EditUserStarSubscription(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_EditUserStarSubscription_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditUserStarSubscriptionParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
IsCanceled: true,
}
_, err := EditUserStarSubscription(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_EditUserStarSubscription_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_EditUserStarSubscription_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := EditUserStarSubscription(context.Background(), bot, &EditUserStarSubscriptionParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_EditUserStarSubscription_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_EditUserStarSubscription_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditUserStarSubscriptionParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
IsCanceled: true,
}
_, err := EditUserStarSubscription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_EditUserStarSubscription_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_EditUserStarSubscription_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &EditUserStarSubscriptionParams{
UserID: 42,
TelegramPaymentChargeID: "test_value",
IsCanceled: true,
}
_, err := EditUserStarSubscription(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetPassportDataErrors_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setPassportDataErrors")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetPassportDataErrorsParams{
UserID: 42,
Errors: nil,
}
_, err := SetPassportDataErrors(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetPassportDataErrors_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetPassportDataErrorsParams{
UserID: 42,
Errors: nil,
}
_, err := SetPassportDataErrors(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetPassportDataErrors_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetPassportDataErrorsParams{
UserID: 42,
Errors: nil,
}
_, err := SetPassportDataErrors(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetPassportDataErrors_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetPassportDataErrorsParams{
UserID: 42,
Errors: nil,
}
_, err := SetPassportDataErrors(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetPassportDataErrors_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetPassportDataErrorsParams{
UserID: 42,
Errors: nil,
}
_, err := SetPassportDataErrors(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetPassportDataErrors_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetPassportDataErrors_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetPassportDataErrors(context.Background(), bot, &SetPassportDataErrorsParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetPassportDataErrors_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetPassportDataErrors_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetPassportDataErrorsParams{
UserID: 42,
Errors: nil,
}
_, err := SetPassportDataErrors(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetPassportDataErrors_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetPassportDataErrors_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetPassportDataErrorsParams{
UserID: 42,
Errors: nil,
}
_, err := SetPassportDataErrors(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SendGame_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/sendGame")
})).Return(genTestResp(200, `{"ok":true,"result":{}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGameParams{
ChatID: ChatIDFromInt(123),
GameShortName: "test_value",
}
_, err := SendGame(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SendGame_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGameParams{
ChatID: ChatIDFromInt(123),
GameShortName: "test_value",
}
_, err := SendGame(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SendGame_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGameParams{
ChatID: ChatIDFromInt(123),
GameShortName: "test_value",
}
_, err := SendGame(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SendGame_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGameParams{
ChatID: ChatIDFromInt(123),
GameShortName: "test_value",
}
_, err := SendGame(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SendGame_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGameParams{
ChatID: ChatIDFromInt(123),
GameShortName: "test_value",
}
_, err := SendGame(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SendGame_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SendGame_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SendGame(context.Background(), bot, &SendGameParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SendGame_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SendGame_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGameParams{
ChatID: ChatIDFromInt(123),
GameShortName: "test_value",
}
_, err := SendGame(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SendGame_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SendGame_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SendGameParams{
ChatID: ChatIDFromInt(123),
GameShortName: "test_value",
}
_, err := SendGame(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_SetGameScore_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/setGameScore")
})).Return(genTestResp(200, `{"ok":true,"result":true}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetGameScoreParams{
UserID: 42,
Score: 42,
}
_, err := SetGameScore(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_SetGameScore_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetGameScoreParams{
UserID: 42,
Score: 42,
}
_, err := SetGameScore(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_SetGameScore_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetGameScoreParams{
UserID: 42,
Score: 42,
}
_, err := SetGameScore(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_SetGameScore_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetGameScoreParams{
UserID: 42,
Score: 42,
}
_, err := SetGameScore(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_SetGameScore_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetGameScoreParams{
UserID: 42,
Score: 42,
}
_, err := SetGameScore(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_SetGameScore_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_SetGameScore_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := SetGameScore(context.Background(), bot, &SetGameScoreParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_SetGameScore_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_SetGameScore_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetGameScoreParams{
UserID: 42,
Score: 42,
}
_, err := SetGameScore(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_SetGameScore_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_SetGameScore_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &SetGameScoreParams{
UserID: 42,
Score: 42,
}
_, err := SetGameScore(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}
func Test_GetGameHighScores_Success(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.MatchedBy(func(r *http.Request) bool {
return strings.HasSuffix(r.URL.Path, "/getGameHighScores")
})).Return(genTestResp(200, `{"ok":true,"result":[]}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetGameHighScoresParams{
UserID: 42,
}
_, err := GetGameHighScores(context.Background(), bot, params)
require.NoError(t, err)
}
func Test_GetGameHighScores_APIError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":429,"description":"Too Many Requests","parameters":{"retry_after":1}}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetGameHighScoresParams{
UserID: 42,
}
_, err := GetGameHighScores(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 429, ae.Code)
require.True(t, ae.IsRetryable())
}
func Test_GetGameHighScores_NetworkError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, errors.New("dial tcp: timeout"))
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetGameHighScoresParams{
UserID: 42,
}
_, err := GetGameHighScores(context.Background(), bot, params)
require.Error(t, err)
var ne *client.NetworkError
require.ErrorAs(t, err, &ne)
}
func Test_GetGameHighScores_ParseError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(genTestResp(200, `not json`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetGameHighScoresParams{
UserID: 42,
}
_, err := GetGameHighScores(context.Background(), bot, params)
require.Error(t, err)
var pe *client.ParseError
require.ErrorAs(t, err, &pe)
}
func Test_GetGameHighScores_ContextCanceled(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(nil, context.Canceled).Maybe()
ctx, cancel := context.WithCancel(context.Background())
cancel()
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetGameHighScoresParams{
UserID: 42,
}
_, err := GetGameHighScores(ctx, bot, params)
require.Error(t, err)
require.ErrorIs(t, err, context.Canceled)
}
// Test_GetGameHighScores_MissingRequiredFields exercises Telegram's server-side
// validation: when a required field is omitted, Telegram returns 400 with
// a description like "Bad Request: <field> is empty". The library must
// surface this as *APIError with the ErrBadRequest sentinel.
func Test_GetGameHighScores_MissingRequiredFields(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
// Send a Params with all required fields zeroed — simulates a caller
// that forgot to populate them. The bot library marshals as-is and
// surfaces Telegram's 400 reply.
_, err := GetGameHighScores(context.Background(), bot, &GetGameHighScoresParams{})
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 400, ae.Code)
require.True(t, errors.Is(err, client.ErrBadRequest))
require.False(t, ae.IsRetryable())
}
// Test_GetGameHighScores_Forbidden exercises the 403 path (bot blocked by user,
// removed from chat, etc.). The library must surface the ErrForbidden
// sentinel.
func Test_GetGameHighScores_Forbidden(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetGameHighScoresParams{
UserID: 42,
}
_, err := GetGameHighScores(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 403, ae.Code)
require.True(t, errors.Is(err, client.ErrForbidden))
require.False(t, ae.IsRetryable())
}
// Test_GetGameHighScores_ServerError exercises the 5xx path. The library must
// classify these as retryable so RetryDoer / user retry logic kicks in.
func Test_GetGameHighScores_ServerError(t *testing.T) {
m := &genTestMockDoer{}
m.On("Do", mock.Anything).Return(
genTestResp(200, `{"ok":false,"error_code":500,"description":"Internal server error"}`), nil)
bot := client.New("test:token", client.WithHTTPClient(m))
params := &GetGameHighScoresParams{
UserID: 42,
}
_, err := GetGameHighScores(context.Background(), bot, params)
require.Error(t, err)
var ae *client.APIError
require.ErrorAs(t, err, &ae)
require.Equal(t, 500, ae.Code)
require.True(t, ae.IsRetryable(), "5xx must be retryable")
}