diff --git a/app.go b/app.go
index fe964c1..f016d4a 100644
--- a/app.go
+++ b/app.go
@@ -36,7 +36,7 @@ type goBlog struct {
pPostHooks []postHookFunc
pUpdateHooks []postHookFunc
pDeleteHooks []postHookFunc
- // HTTP
+ // HTTP Routers
d *dynamicHandler
privateMode bool
privateModeHandler []func(http.Handler) http.Handler
diff --git a/database_test.go b/database_test.go
index c431848..6108eae 100644
--- a/database_test.go
+++ b/database_test.go
@@ -4,6 +4,10 @@ import (
"testing"
)
+func (a *goBlog) setInMemoryDatabase() {
+ a.db, _ = a.openDatabase(":memory:", false)
+}
+
func Test_database(t *testing.T) {
t.Run("Basic Database Test", func(t *testing.T) {
app := &goBlog{}
diff --git a/httpClient.go b/httpClient.go
index e275587..d674dc5 100644
--- a/httpClient.go
+++ b/httpClient.go
@@ -5,7 +5,11 @@ import (
"time"
)
-var appHttpClient = &http.Client{
+type httpClient interface {
+ Do(req *http.Request) (*http.Response, error)
+}
+
+var appHttpClient httpClient = &http.Client{
Timeout: 5 * time.Minute,
Transport: &http.Transport{
DisableKeepAlives: true,
diff --git a/httpClient_test.go b/httpClient_test.go
new file mode 100644
index 0000000..be1e497
--- /dev/null
+++ b/httpClient_test.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+ "io"
+ "net/http"
+ "strings"
+ "sync"
+)
+
+type fakeHttpClient struct {
+ req *http.Request
+ res *http.Response
+ err error
+ enabled bool
+ // internal
+ alt httpClient
+ mx sync.Mutex
+}
+
+var fakeAppHttpClient *fakeHttpClient
+
+func init() {
+ fakeAppHttpClient = &fakeHttpClient{
+ alt: appHttpClient,
+ }
+ appHttpClient = fakeAppHttpClient
+}
+
+func (c *fakeHttpClient) Do(req *http.Request) (*http.Response, error) {
+ if !c.enabled {
+ return c.alt.Do(req)
+ }
+ c.req = req
+ return c.res, c.err
+}
+
+func (c *fakeHttpClient) clean() {
+ c.req = nil
+ c.err = nil
+ c.res = nil
+}
+
+func (c *fakeHttpClient) setFakeResponse(statusCode int, body string, err error) {
+ c.clean()
+ c.err = err
+ c.res = &http.Response{
+ StatusCode: statusCode,
+ Body: io.NopCloser(strings.NewReader(body)),
+ }
+}
+
+func (c *fakeHttpClient) lock(enabled bool) {
+ c.mx.Lock()
+ c.clean()
+ c.enabled = enabled
+}
+
+func (c *fakeHttpClient) unlock() {
+ c.enabled = false
+ c.clean()
+ c.mx.Unlock()
+}
diff --git a/notifications.go b/notifications.go
index 9c0a23e..9cd8fe6 100644
--- a/notifications.go
+++ b/notifications.go
@@ -30,11 +30,9 @@ func (a *goBlog) sendNotification(text string) {
log.Println("Failed to save notification:", err.Error())
}
if an := a.cfg.Notifications; an != nil {
- if tg := an.Telegram; tg != nil && tg.Enabled {
- err := sendTelegramMessage(n.Text, "", tg.BotToken, tg.ChatID)
- if err != nil {
- log.Println("Failed to send Telegram notification:", err.Error())
- }
+ err := an.Telegram.send(n.Text, "")
+ if err != nil {
+ log.Println("Failed to send Telegram notification:", err.Error())
}
}
}
diff --git a/telegram.go b/telegram.go
index 083ef5f..4299942 100644
--- a/telegram.go
+++ b/telegram.go
@@ -14,24 +14,27 @@ import (
const telegramBaseURL = "https://api.telegram.org/bot"
func (a *goBlog) initTelegram() {
- enable := false
- for _, b := range a.cfg.Blogs {
- if tg := b.Telegram; tg != nil && tg.Enabled && tg.BotToken != "" && tg.ChatID != "" {
- enable = true
- }
- }
- if enable {
- a.pPostHooks = append(a.pPostHooks, func(p *post) {
- if p.isPublishedSectionPost() {
- tgPost(a.cfg.Blogs[p.Blog].Telegram, p.title(), a.fullPostURL(p), a.shortPostURL(p))
+ a.pPostHooks = append(a.pPostHooks, func(p *post) {
+ if tg := a.cfg.Blogs[p.Blog].Telegram; tg.enabled() && p.isPublishedSectionPost() {
+ if html := tg.generateHTML(p.title(), a.fullPostURL(p), a.shortPostURL(p)); html != "" {
+ if err := tg.send(html, "HTML"); err != nil {
+ log.Printf("Failed to send post to Telegram: %v", err)
+ }
}
- })
- }
+ }
+ })
}
-func tgPost(tg *configTelegram, title, fullURL, shortURL string) {
+func (tg *configTelegram) enabled() bool {
if tg == nil || !tg.Enabled || tg.BotToken == "" || tg.ChatID == "" {
- return
+ return false
+ }
+ return true
+}
+
+func (tg *configTelegram) generateHTML(title, fullURL, shortURL string) string {
+ if !tg.enabled() {
+ return ""
}
replacer := strings.NewReplacer("<", "<", ">", ">", "&", "&")
var message bytes.Buffer
@@ -48,19 +51,20 @@ func tgPost(tg *configTelegram, title, fullURL, shortURL string) {
message.WriteString(replacer.Replace(shortURL))
message.WriteString("")
}
- if err := sendTelegramMessage(message.String(), "HTML", tg.BotToken, tg.ChatID); err != nil {
- log.Println(err.Error())
- }
+ return message.String()
}
-func sendTelegramMessage(message, mode, token, chat string) error {
+func (tg *configTelegram) send(message, mode string) error {
+ if !tg.enabled() {
+ return nil
+ }
params := url.Values{}
- params.Add("chat_id", chat)
+ params.Add("chat_id", tg.ChatID)
params.Add("text", message)
if mode != "" {
params.Add("parse_mode", mode)
}
- tgURL, err := url.Parse(telegramBaseURL + token + "/sendMessage")
+ tgURL, err := url.Parse(telegramBaseURL + tg.BotToken + "/sendMessage")
if err != nil {
return errors.New("failed to create Telegram request")
}
diff --git a/telegram_test.go b/telegram_test.go
new file mode 100644
index 0000000..7e07fea
--- /dev/null
+++ b/telegram_test.go
@@ -0,0 +1,193 @@
+package main
+
+import (
+ "net/http"
+ "testing"
+ "time"
+)
+
+func Test_configTelegram_enabled(t *testing.T) {
+ if (&configTelegram{}).enabled() == true {
+ t.Error("Telegram shouldn't be enabled")
+ }
+
+ if (&configTelegram{
+ Enabled: true,
+ }).enabled() == true {
+ t.Error("Telegram shouldn't be enabled")
+ }
+
+ if (&configTelegram{
+ Enabled: true,
+ ChatID: "abc",
+ }).enabled() == true {
+ t.Error("Telegram shouldn't be enabled")
+ }
+
+ if (&configTelegram{
+ Enabled: true,
+ BotToken: "abc",
+ }).enabled() == true {
+ t.Error("Telegram shouldn't be enabled")
+ }
+
+ if (&configTelegram{
+ Enabled: true,
+ BotToken: "abc",
+ ChatID: "abc",
+ }).enabled() != true {
+ t.Error("Telegram should be enabled")
+ }
+}
+
+func Test_configTelegram_generateHTML(t *testing.T) {
+ tg := &configTelegram{
+ Enabled: true,
+ ChatID: "abc",
+ BotToken: "abc",
+ }
+
+ // Without Instant View
+
+ expected := "Title\n\nhttps://example.com/s/1"
+ if got := tg.generateHTML("Title", "https://example.com/test", "https://example.com/s/1"); got != expected {
+ t.Errorf("Wrong result, got: %v", got)
+ }
+
+ // With Instant View
+
+ tg.InstantViewHash = "abc"
+ expected = "Title\n\nhttps://example.com/s/1"
+ if got := tg.generateHTML("Title", "https://example.com/test", "https://example.com/s/1"); got != expected {
+ t.Errorf("Wrong result, got: %v", got)
+ }
+}
+
+func Test_configTelegram_send(t *testing.T) {
+ fakeAppHttpClient.lock(true)
+ defer fakeAppHttpClient.unlock()
+
+ tg := &configTelegram{
+ Enabled: true,
+ ChatID: "chatid",
+ BotToken: "bottoken",
+ }
+
+ fakeAppHttpClient.setFakeResponse(200, "", nil)
+
+ err := tg.send("Message", "HTML")
+ if err != nil {
+ t.Errorf("Error: %v", err)
+ }
+
+ if fakeAppHttpClient.req == nil {
+ t.Error("Empty request")
+ }
+ if fakeAppHttpClient.err != nil {
+ t.Error("Error in request")
+ }
+ if fakeAppHttpClient.req.Method != http.MethodPost {
+ t.Error("Wrong method")
+ }
+ if u := fakeAppHttpClient.req.URL.String(); u != "https://api.telegram.org/botbottoken/sendMessage?chat_id=chatid&parse_mode=HTML&text=Message" {
+ t.Errorf("Wrong request URL, got: %v", u)
+ }
+}
+
+func Test_goBlog_initTelegram(t *testing.T) {
+ app := &goBlog{
+ pPostHooks: []postHookFunc{},
+ }
+
+ app.initTelegram()
+
+ if len(app.pPostHooks) != 1 {
+ t.Error("Hook not registered")
+ }
+}
+
+func Test_telegram(t *testing.T) {
+ t.Run("Send post to Telegram", func(t *testing.T) {
+ fakeAppHttpClient.lock(true)
+ defer fakeAppHttpClient.unlock()
+
+ fakeAppHttpClient.setFakeResponse(200, "", nil)
+
+ app := &goBlog{
+ pPostHooks: []postHookFunc{},
+ cfg: &config{
+ Server: &configServer{
+ PublicAddress: "https://example.com",
+ },
+ Blogs: map[string]*configBlog{
+ "en": {
+ Telegram: &configTelegram{
+ Enabled: true,
+ ChatID: "chatid",
+ BotToken: "bottoken",
+ },
+ },
+ },
+ },
+ }
+ app.setInMemoryDatabase()
+
+ app.initTelegram()
+
+ p := &post{
+ Path: "/test",
+ Parameters: map[string][]string{
+ "title": {"Title"},
+ },
+ Published: time.Now().String(),
+ Section: "test",
+ Blog: "en",
+ Status: statusPublished,
+ }
+
+ app.pPostHooks[0](p)
+
+ if u := fakeAppHttpClient.req.URL.String(); u != "https://api.telegram.org/botbottoken/sendMessage?chat_id=chatid&parse_mode=HTML&text=Title%0A%0A%3Ca+href%3D%22https%3A%2F%2Fexample.com%2Fs%2F1%22%3Ehttps%3A%2F%2Fexample.com%2Fs%2F1%3C%2Fa%3E" {
+ t.Errorf("Wrong request URL, got: %v", u)
+ }
+ })
+
+ t.Run("Telegram disabled", func(t *testing.T) {
+ fakeAppHttpClient.lock(true)
+ defer fakeAppHttpClient.unlock()
+
+ fakeAppHttpClient.setFakeResponse(200, "", nil)
+
+ app := &goBlog{
+ pPostHooks: []postHookFunc{},
+ cfg: &config{
+ Server: &configServer{
+ PublicAddress: "https://example.com",
+ },
+ Blogs: map[string]*configBlog{
+ "en": {},
+ },
+ },
+ }
+ app.setInMemoryDatabase()
+
+ app.initTelegram()
+
+ p := &post{
+ Path: "/test",
+ Parameters: map[string][]string{
+ "title": {"Title"},
+ },
+ Published: time.Now().String(),
+ Section: "test",
+ Blog: "en",
+ Status: statusPublished,
+ }
+
+ app.pPostHooks[0](p)
+
+ if fakeAppHttpClient.req != nil {
+ t.Error("There should be no request")
+ }
+ })
+}