Files
go-telegram/api/methods_gen_test.go
T
lukaszraczylo 3c04d7b0b1 feat(api): typed enums for all string-enum fields
The Telegram docs describe many string fields and parameters with
phrases like "can be ..., or ...", "must be one of ...", or "always X",
yet the generated Go API surface used raw `string` for every one of
them. Callers had to write magic strings or `string(api.ChatTypePrivate)`
to satisfy the field type. This change makes those fields typed Go
string enums emitted from the IR, so the IDE autocompletes valid values
and breaking-value drift surfaces at compile time.

Pipeline changes:

- internal/spec/ir.go: Field gains EnumValues []string. Empty for non-
  enum fields; otherwise the wire-level values in doc order, deduped.

- cmd/scrape/enums.go: extractEnumValues recognises the curly-quoted
  patterns Telegram uses ("can be either", "currently can be", "one
  of", "must be", "always X") and rejects free-text quoted refs (e.g.
  "Can be available only for X") via a tight gap check between the
  trigger phrase and the first quoted value. parse_mode parameters
  get the canonical Markdown / MarkdownV2 / HTML triple injected
  because Telegram links to a separate formatting-options section
  instead of listing values inline.

- cmd/genapi/enums.go: planEnums groups fields by sorted value-tuple,
  picks a canonical Go enum name (most-common candidate, parent-
  prefixed beats plain, shortest beats longer, alphabetical for
  determinism), resolves cross-group name collisions by parent prefix.

- cmd/genapi/emitter.go + templates: goField rewrites the field type
  to the planned enum name; multipartFieldEntry casts typed enum
  values back to string when composing the wire map; enums.tmpl now
  iterates the planned enums instead of hardcoding four hand-curated
  ones; sentinelForField produces typed-constant test fixtures.

- api/enums.gen.go: regenerated from the live IR. 66 enum types, 155
  constants. ParseMode, ChatType, MessageEntityType, ChatMember /
  MessageOrigin / PaidMedia / Background / StoryAreaType / Reaction /
  TransactionPartner / PassportElement variant Status & Type fields
  are now typed.

- api/enums.go: hand-coded UpdateType (used by transport.LongPoller).
  The Telegram docs do not enumerate Update payload kinds inline, so
  the codegen pipeline cannot synthesise this enum.

- api/types.gen.go, api/methods.gen.go, api/methods_gen_test.go: 137
  field declarations rewritten string -> typed enum.

- dispatch/, examples/: dropped every string(api.<Const>) cast. The
  HasEntity filter now takes api.MessageEntityType; ChatType filter
  compares typed values directly. ChatMember discriminator filter
  casts variant.Status (typed per variant) to string for comparison.

- internal/spec/api.json, testdata/golden/*: regenerated and
  refreshed. make regen-from-fixture is byte-deterministic across
  runs.

Renames (no compat shims; v1 pre-public):
- EntityX  -> MessageEntityTypeX  (e.g. EntityBotCommand -> MessageEntityTypeBotCommand)
- EntityStrike -> MessageEntityTypeStrikethrough (full wire name)
2026-05-09 17:55:34 +01:00

24704 lines
871 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_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{
Text: "test_value",
}
_, 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{
Text: "test_value",
}
_, 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{
Text: "test_value",
}
_, 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{
Text: "test_value",
}
_, 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{
Text: "test_value",
}
_, 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{
Text: "test_value",
}
_, 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{
Text: "test_value",
}
_, 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_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")
}