// 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: "test_value", } _, 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: "test_value", } _, 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: "test_value", } _, 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: "test_value", } _, 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: "test_value", } _, 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: 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: "test_value", } _, 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: "test_value", } _, 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: "test_value", } _, 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: "test_value", } _, 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: "test_value", } _, 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: "test_value", } _, 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: "test_value", } _, 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: 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: "test_value", } _, 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: "test_value", } _, 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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") }