Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some unit tests #92

Merged
merged 3 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package bot

import (
"errors"
"testing"
)

func TestTooManyRequestsError(t *testing.T) {
err := &TooManyRequestsError{
Message: "rate limit exceeded",
RetryAfter: 30,
}

expectedErrorMsg := "rate limit exceeded: retry_after 30"
if err.Error() != expectedErrorMsg {
t.Errorf("expected %s, got %s", expectedErrorMsg, err.Error())
}

if !IsTooManyRequestsError(err) {
t.Errorf("expected IsTooManyRequestsError to return true")
}

var genericError error = err
if !IsTooManyRequestsError(genericError) {
t.Errorf("expected IsTooManyRequestsError to return true for generic error type")
}
}

func TestMigrateError(t *testing.T) {
err := &MigrateError{
Message: "chat migrated",
MigrateToChatID: 12345,
}

expectedErrorMsg := "chat migrated: migrate_to_chat_id 12345"
if err.Error() != expectedErrorMsg {
t.Errorf("expected %s, got %s", expectedErrorMsg, err.Error())
}

if !IsMigrateError(err) {
t.Errorf("expected IsMigrateError to return true")
}

var genericError error = err
if !IsMigrateError(genericError) {
t.Errorf("expected IsMigrateError to return true for generic error type")
}
}

func TestStandardErrors(t *testing.T) {
tests := []struct {
err error
expected string
}{
{ErrorForbidden, "forbidden"},
{ErrorBadRequest, "bad request"},
{ErrorUnauthorized, "unauthorized"},
{ErrorTooManyRequests, "too many requests"},
{ErrorNotFound, "not found"},
{ErrorConflict, "conflict"},
}

for _, tt := range tests {
if tt.err.Error() != tt.expected {
t.Errorf("expected %s, got %s", tt.expected, tt.err.Error())
}
}
}

func TestIsTooManyRequestsErrorFalse(t *testing.T) {
err := errors.New("some other error")
if IsTooManyRequestsError(err) {
t.Errorf("expected IsTooManyRequestsError to return false")
}
}

func TestIsMigrateErrorFalse(t *testing.T) {
err := errors.New("some other error")
if IsMigrateError(err) {
t.Errorf("expected IsMigrateError to return false")
}
}
134 changes: 134 additions & 0 deletions process_update_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package bot

import (
"context"
"sync"
"testing"

"github.com/go-telegram/bot/models"
)

func Test_applyMiddlewares(t *testing.T) {
h := func(ctx context.Context, bot *Bot, update *models.Update) {}

middleware1 := func(next HandlerFunc) HandlerFunc {
return func(ctx context.Context, bot *Bot, update *models.Update) {
next(ctx, bot, update)
}
}

middleware2 := func(next HandlerFunc) HandlerFunc {
return func(ctx context.Context, bot *Bot, update *models.Update) {
next(ctx, bot, update)
}
}

wrapped := applyMiddlewares(h, middleware1, middleware2)
if wrapped == nil {
t.Fatal("Expected wrapped handler to be non-nil")
}
}

func TestProcessUpdate(t *testing.T) {
var called bool
h := func(ctx context.Context, bot *Bot, update *models.Update) {
called = true
}

bot := &Bot{
defaultHandlerFunc: h,
middlewares: []Middleware{},
handlersMx: &sync.RWMutex{},
handlers: map[string]handler{},
}

ctx := context.Background()
upd := &models.Update{Message: &models.Message{Text: "test"}}

bot.ProcessUpdate(ctx, upd)
if !called {
t.Fatal("Expected default handler to be called")
}
}

func TestProcessUpdate_WithMiddlewares(t *testing.T) {
var called bool
h := func(ctx context.Context, bot *Bot, update *models.Update) {
called = true
}

middleware := func(next HandlerFunc) HandlerFunc {
return func(ctx context.Context, bot *Bot, update *models.Update) {
next(ctx, bot, update)
}
}

bot := &Bot{
defaultHandlerFunc: h,
middlewares: []Middleware{middleware},
handlersMx: &sync.RWMutex{},
handlers: map[string]handler{},
}

ctx := context.Background()
upd := &models.Update{Message: &models.Message{Text: "test"}}

bot.ProcessUpdate(ctx, upd)
if !called {
t.Fatal("Expected default handler to be called")
}
}

func Test_findHandler(t *testing.T) {
var called bool
h := func(ctx context.Context, bot *Bot, update *models.Update) {
called = true
}

bot := &Bot{
defaultHandlerFunc: h,
handlersMx: &sync.RWMutex{},
handlers: map[string]handler{},
}

// Register a handler
bot.handlers["test"] = handler{
handlerType: HandlerTypeMessageText,
matchType: MatchTypeExact,
pattern: "test",
handler: h,
}

ctx := context.Background()
upd := &models.Update{Message: &models.Message{Text: "test"}}

handler := bot.findHandler(HandlerTypeMessageText, upd)
handler(ctx, bot, upd)

if !called {
t.Fatal("Expected registered handler to be called")
}
}

func Test_findHandler_Default(t *testing.T) {
var called bool
h := func(ctx context.Context, bot *Bot, update *models.Update) {
called = true
}

bot := &Bot{
defaultHandlerFunc: h,
handlersMx: &sync.RWMutex{},
handlers: map[string]handler{},
}

ctx := context.Background()
upd := &models.Update{Message: &models.Message{Text: "test"}}

handler := bot.findHandler(HandlerTypeCallbackQueryData, upd)
handler(ctx, bot, upd)

if !called {
t.Fatal("Expected default handler to be called")
}
}
6 changes: 6 additions & 0 deletions webhook_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ func (b *Bot) WebhookHandler() http.HandlerFunc {
case <-req.Context().Done():
b.error("some updates lost, ctx done")
return
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code confuses me. What is the essence of his change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@negasus
In the first select block, default: is used to ensure that if the context has been canceled, it will immediately exit the select block and execute the error handling logic without waiting. This helps avoid sending the update if the context is already invalid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With default: The code checks for context cancellation non-blockingly, allowing it to proceed immediately if the context is not canceled. And if without default: then code will block until the context is canceled, potentially causing delays and inefficiencies.

Copy link
Contributor Author

@renatosaksanni renatosaksanni Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in summary the first select block will checks if the context is canceled. If it is, it logs an error and returns without sending the update to the channel.

And the second select block will sends the update to the bot.updates channel, or logs an error if the context is canceled during the send operation.

@negasus just to give you some context to the line changes, due to I ran into this error while creating and running the webhook_handler unit test:

FAIL: TestWebhookHandler_ContextDone (0.00s)
webhook_handler_test.go:171: Received update despite context cancellation

}

select {
case b.updates <- update:
case <-req.Context().Done():
b.error("failed to send update, ctx done")
}
}
}
Loading
Loading