Skip to content
Closed
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
14 changes: 12 additions & 2 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// It also handles API errors, so you only need to unwrap
// result field from json data.
func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) {
url := b.URL + "/bot" + b.Token + "/" + method
url := b.url(method)

var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(payload); err != nil {
Expand All @@ -36,7 +36,7 @@ func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) {

go func() {
select {
case <-b.stopClient:
case <-b.getChanStopClient():
cancel()
case <-exit:
}
Expand Down Expand Up @@ -68,6 +68,16 @@ func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) {
return data, extractOk(data)
}

func (b *Bot) url(method string) string {
url := b.URL + "/bot" + b.Token

if b.useTestEnv {
url += "/test"
}

return url + "/" + method
}

func (b *Bot) sendFiles(method string, files map[string]File, params map[string]string) ([]byte, error) {
rawFiles := make(map[string]interface{})
for name, f := range files {
Expand Down
36 changes: 34 additions & 2 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package telebot
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// testPayload implements json.Marshaler
Expand Down Expand Up @@ -118,3 +118,35 @@ func TestExtractMessage(t *testing.T) {
_, err = extractMessage(data)
require.NoError(t, err)
}

func TestBot_url(t *testing.T) {
url := "https://api.telegram.com"
token := "my-token"
method := "my-method"

tests := []struct {
name string
useTestEnv bool
expectedURL string
}{
{"when bot is not set to test environment", false, fmt.Sprintf("%s/bot%s/%s", url, token, method)},
{"when bot is set to test environment", true, fmt.Sprintf("%s/bot%s/test/%s", url, token, method)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
settings := Settings{
URL: url,
Token: token,
Offline: true,
UseTestEnv: tt.useTestEnv,
}
bot, err := NewBot(settings)

assert.NoError(t, err)

url := bot.url(method)

assert.Equal(t, tt.expectedURL, url)
})
}
}
20 changes: 15 additions & 5 deletions bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
)

Expand Down Expand Up @@ -48,6 +49,7 @@ func NewBot(pref Settings) (*Bot, error) {
synchronous: pref.Synchronous,
verbose: pref.Verbose,
parseMode: pref.ParseMode,
useTestEnv: pref.UseTestEnv,
client: client,
}

Expand Down Expand Up @@ -79,9 +81,12 @@ type Bot struct {
synchronous bool
verbose bool
parseMode ParseMode
useTestEnv bool
stop chan chan struct{}
client *http.Client
stopClient chan struct{}

stopClientMux sync.RWMutex
}

// Settings represents a utility struct for passing certain
Expand Down Expand Up @@ -119,6 +124,10 @@ type Settings struct {

// Offline allows to create a bot without network for testing purposes.
Offline bool

// UseTestEnv is used to create a bot on Telegram test environment
// https://core.telegram.org/bots/webapps#using-bots-in-the-test-environment
UseTestEnv bool
}

var defaultOnError = func(err error, c Context) {
Expand Down Expand Up @@ -198,10 +207,11 @@ func (b *Bot) Start() {
}

// do nothing if called twice
if b.stopClient != nil {
if b.getChanStopClient() != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

We're still racing here, it would be better to encapsulate nil checking and creating into a one mutex call. Same with the stop.

return
}
b.stopClient = make(chan struct{})

b.createNewChanStopClient()

stop := make(chan struct{})
stopConfirm := make(chan struct{})
Expand All @@ -221,16 +231,16 @@ func (b *Bot) Start() {
close(stop)
<-stopConfirm
close(confirm)
b.stopClient = nil
b.destroyChanStopClient()
return
}
}
}

// Stop gracefully shuts the poller down.
func (b *Bot) Stop() {
if b.stopClient != nil {
close(b.stopClient)
if b.getChanStopClient() != nil {
b.closeChanStopClient()
}
confirm := make(chan struct{})
b.stop <- confirm
Expand Down
2 changes: 2 additions & 0 deletions bot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func TestNewBot(t *testing.T) {
pref.Poller = &LongPoller{Timeout: time.Second}
pref.Updates = 50
pref.ParseMode = ModeHTML
pref.UseTestEnv = true
pref.Offline = true

b, err = NewBot(pref)
Expand All @@ -73,6 +74,7 @@ func TestNewBot(t *testing.T) {
assert.Equal(t, pref.Poller, b.Poller)
assert.Equal(t, 50, cap(b.Updates))
assert.Equal(t, ModeHTML, b.parseMode)
assert.Equal(t, pref.UseTestEnv, b.useTestEnv)
}

func TestBotHandle(t *testing.T) {
Expand Down
29 changes: 29 additions & 0 deletions channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package telebot

func (b *Bot) getChanStopClient() chan struct{} {
b.stopClientMux.RLock()
defer b.stopClientMux.RUnlock()

return b.stopClient
}

func (b *Bot) closeChanStopClient() {
b.stopClientMux.Lock()
defer b.stopClientMux.Unlock()

close(b.stopClient)
}

func (b *Bot) createNewChanStopClient() {
b.stopClientMux.Lock()
defer b.stopClientMux.Unlock()

b.stopClient = make(chan struct{})
}

func (b *Bot) destroyChanStopClient() {
b.stopClientMux.Lock()
defer b.stopClientMux.Unlock()

b.stopClient = nil
}
47 changes: 47 additions & 0 deletions channel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package telebot

import (
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestRaceStopClientChannel(t *testing.T) {
t.Parallel()

api, err := NewBot(Settings{Offline: true})
require.NoError(t, err)

syncChan := make(chan struct{})

go func() {
syncChan <- struct{}{}

_, err = api.Raw("setMyCommands", CommandParams{
Commands: []Command{
{
Text: "/test",
Description: "test",
},
},
LanguageCode: "en",
})
require.EqualError(t, err, "telegram: Not Found (404)")

close(syncChan)
}()

<-syncChan

time.Sleep(time.Second)

// act and assert
go api.Start()
defer func() {
_, err = api.Close()
require.NoError(t, err)
}()

<-syncChan
}