Compare commits
12 Commits
41bdd6685a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47b694c477 | ||
|
|
f72b510b65 | ||
|
|
2ef9199c9a | ||
|
|
77bddef812 | ||
|
|
e34866abe2 | ||
|
|
59643b6a0a | ||
|
|
57b2c56b3f | ||
|
|
9bb8eba492 | ||
|
|
6c8e735d04 | ||
|
|
3afe728650 | ||
|
|
b661d42074 | ||
|
|
97b337db31 |
18
Makefile
18
Makefile
@@ -8,6 +8,19 @@ UNIX_BINARY=${MYNAME}
|
||||
WINDOWS_BINARY=${MYNAME}.exe
|
||||
RMCMD=rm -rf
|
||||
|
||||
# Default target
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "aleesa-misc-go build system"
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@echo " all Clean and build (default)"
|
||||
@echo " build Build binary only"
|
||||
@echo " clean Remove built artifacts"
|
||||
@echo " test Run unit tests"
|
||||
@echo " upgrade Update dependencies (get, tidy, vendor)"
|
||||
@echo ""
|
||||
|
||||
# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe
|
||||
# не нужен
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@@ -30,6 +43,7 @@ define IFS
|
||||
|
||||
endef
|
||||
|
||||
.PHONY: all build clean test upgrade help
|
||||
|
||||
all: clean build
|
||||
|
||||
@@ -69,6 +83,10 @@ else # not ifeq ($(OS),Windows_NT)
|
||||
endif
|
||||
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
|
||||
upgrade:
|
||||
go get -u ./...
|
||||
go mod tidy
|
||||
|
||||
7
TODO
7
TODO
@@ -1,7 +0,0 @@
|
||||
Добавить в протокол секцию nsfw/censor.
|
||||
|
||||
В качестве валидатора использовать какую-нить готовую либу
|
||||
https://gookit.github.io/validate/#/
|
||||
github.com/go-playground/validator/v10
|
||||
|
||||
https://awesome-go.com/validation/
|
||||
@@ -28,6 +28,8 @@ func main() {
|
||||
log.Error(fmt.Sprintf("Unable to open log file %s: %s", misc.Config.Log, err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
defer logfile.Close()
|
||||
} else {
|
||||
logfile = os.Stderr
|
||||
}
|
||||
@@ -36,11 +38,24 @@ func main() {
|
||||
|
||||
// Иницализируем клиента Редиски.
|
||||
misc.RedisClient = redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", misc.Config.Server, misc.Config.Port),
|
||||
}).WithContext(misc.Ctx).WithTimeout(time.Duration(misc.Config.Timeout) * time.Second)
|
||||
Addr: fmt.Sprintf("%s:%d", misc.Config.Server, misc.Config.Port),
|
||||
MaxRetries: -1,
|
||||
MinRetryBackoff: time.Second,
|
||||
MaxRetryBackoff: 30 * time.Second,
|
||||
}).WithContext(misc.RedisCtx).WithTimeout(time.Duration(misc.Config.Timeout) * time.Second)
|
||||
|
||||
// Проверим, что редиска доступна. Повторяем попытки, пока не подключится.
|
||||
for {
|
||||
if err := misc.RedisClient.Ping(misc.RedisCtx).Err(); err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Warnf("Unable to connect to redis: %s, retrying...", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
// Обозначим, что хотим после соединения подписаться на события из канала config.Channel.
|
||||
misc.Subscriber = misc.RedisClient.Subscribe(misc.Ctx, misc.Config.Channel)
|
||||
misc.Subscriber = misc.RedisClient.Subscribe(misc.RedisCtx, misc.Config.Channel)
|
||||
|
||||
// Самое время поставить траппер сигналов.
|
||||
signal.Notify(misc.SigChan,
|
||||
@@ -56,9 +71,7 @@ func main() {
|
||||
log.Info("Aleesa-misc-go started")
|
||||
|
||||
for msg := range ch {
|
||||
if !misc.Shutdown {
|
||||
misc.MsgParser(misc.Ctx, msg.Payload)
|
||||
}
|
||||
misc.MsgParser(misc.RedisCtx, msg.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package misc
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
@@ -17,13 +18,17 @@ var ForwardMax int64 = 5
|
||||
var RedisClient *redis.Client
|
||||
var Subscriber *redis.PubSub
|
||||
|
||||
// Ставится в true, если мы получили сигнал на выключение.
|
||||
var Shutdown = false
|
||||
|
||||
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
||||
var SigChan = make(chan os.Signal, 1)
|
||||
|
||||
// Main context.
|
||||
var Ctx = context.Background()
|
||||
// RedisCtx - контекст для операций с редиской. Отменяется при завершении.
|
||||
var RedisCtx context.Context
|
||||
var CancelRedisCtx context.CancelFunc
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func init() {
|
||||
RedisCtx, CancelRedisCtx = context.WithCancel(context.Background())
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
|
||||
@@ -4,10 +4,41 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"aleesa-misc-go/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
retryAttempts = 3
|
||||
retryDelay = time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
commandsPhrases = []string{
|
||||
"friday", "пятница", "proverb", "пословица", "пословиться", "fortune", "фортунка", "f", "ф",
|
||||
"karma", "карма", "rum", "ром", "vodka", "водка", "beer", "пиво", "tequila", "текила", "whisky", "виски",
|
||||
"absinthe", "абсент", "fuck",
|
||||
}
|
||||
commandsPhrasesPrefix = []string{
|
||||
"karma ", "карма ", "rum ", "ром ", "vodka ", "водка ", "beer ", "пиво ", "tequila ",
|
||||
"текила ", "whisky ", "виски ", "absinthe ", "абсент ",
|
||||
}
|
||||
commandsWebappGo = []string{
|
||||
"frog", "лягушка", "horse", "лошадь", "лошадка", "rabbit", "bunny", "кролик",
|
||||
"snail", "улитка", "cat", "кис", "fox", "лис", "buni", "anek", "анек", "анекдот",
|
||||
"xkcd", "monkeyuser", "tits", "boobs", "tities", "boobies", "сиси", "сисечки", "butt",
|
||||
"booty", "ass", "попа", "попка", "drink", "праздник", "owl", "сова", "сыч", "w", "п", "погода",
|
||||
"погодка", "погадка",
|
||||
}
|
||||
commandsWebappGoPrefix = []string{
|
||||
"w ", "п ", "погода ", "погодка ", "погадка ", "weather ",
|
||||
}
|
||||
commandsGames = []string{
|
||||
"dig", "копать", "fish", "fishing", "рыба", "рыбка", "рыбалка",
|
||||
}
|
||||
)
|
||||
|
||||
// MsgParser горутинка, которая парсит json-чики прилетевшие из REDIS-ки.
|
||||
func MsgParser(ctx context.Context, msg string) {
|
||||
var (
|
||||
@@ -15,7 +46,7 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
j rMsg
|
||||
)
|
||||
|
||||
log.Debugf("Incomming raw json: %s", msg)
|
||||
log.Debugf("Incoming raw json: %s", msg)
|
||||
|
||||
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
||||
log.Warnf("Unable to to parse message from redis channel: %s", err)
|
||||
@@ -69,9 +100,10 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
j.Misc.Csign = Config.Csign
|
||||
}
|
||||
|
||||
// j.Misc.Fwdcnt если нам его не передали, то будет 0.
|
||||
if exist := j.Misc.Fwdcnt; exist == 0 {
|
||||
j.Misc.Fwdcnt = 1
|
||||
// j.Misc.Fwdcnt если нам его не передали, то будет nil.
|
||||
if j.Misc.Fwdcnt == nil {
|
||||
j.Misc.Fwdcnt = new(int64)
|
||||
*j.Misc.Fwdcnt = 1
|
||||
}
|
||||
|
||||
// j.Misc.GoodMorning может быть 1 или 0, по-умолчанию 0.
|
||||
@@ -81,27 +113,23 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
// Отвалидировались, теперь вернёмся к нашим баранам.
|
||||
|
||||
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение.
|
||||
if j.Misc.Fwdcnt > Config.ForwardsMax {
|
||||
if *j.Misc.Fwdcnt > Config.ForwardsMax {
|
||||
log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
j.Misc.Fwdcnt++
|
||||
*j.Misc.Fwdcnt++
|
||||
|
||||
// Классифицирем входящие сообщения. Первым делом, попробуем определить команды.
|
||||
if j.Message[0:len(j.Misc.Csign)] == j.Misc.Csign {
|
||||
if len(j.Message) >= len(j.Misc.Csign) && j.Message[0:len(j.Misc.Csign)] == j.Misc.Csign {
|
||||
// Может быть, это команда модуля phrases?
|
||||
var (
|
||||
done = false
|
||||
cmd = j.Message[len(j.Misc.Csign):]
|
||||
)
|
||||
|
||||
cmds := []string{"friday", "пятница", "proverb", "пословица", "пословиться", "fortune", "фортунка", "f", "ф",
|
||||
"karma", "карма", "rum", "ром", "vodka", "водка", "beer", "пиво", "tequila", "текила", "whisky", "виски",
|
||||
"absinthe", "абсент", "fuck"}
|
||||
|
||||
for _, command := range cmds {
|
||||
for _, command := range commandsPhrases {
|
||||
if cmd == command {
|
||||
sendTo = Config.ForwardChannels.Phrases
|
||||
|
||||
@@ -118,13 +146,7 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
|
||||
// Не угадали? акей, как насчёт модуля webapp-go?
|
||||
if !done {
|
||||
cmds = []string{"frog", "лягушка", "horse", "лошадь", "лошадка", "rabbit", "bunny", "кролик",
|
||||
"snail", "улитка", "cat", "кис", "fox", "лис", "buni", "anek", "анек", "анекдот",
|
||||
"xkcd", "monkeyuser", "tits", "boobs", "tities", "boobies", "сиси", "сисечки", "butt",
|
||||
"booty", "ass", "попа", "попка", "drink", "праздник", "owl", "сова", "сыч", "w", "п", "погода",
|
||||
"погодка", "погадка"}
|
||||
|
||||
for _, command := range cmds {
|
||||
for _, command := range commandsWebappGo {
|
||||
if cmd == command {
|
||||
sendTo = Config.ForwardChannels.WebappGo
|
||||
done = true
|
||||
@@ -136,9 +158,7 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
|
||||
// Не фортануло? может, повезёт с модулем games?
|
||||
if !done {
|
||||
cmds = []string{"dig", "копать", "fish", "fishing", "рыба", "рыбка", "рыбалка"}
|
||||
|
||||
for _, command := range cmds {
|
||||
for _, command := range commandsGames {
|
||||
if cmd == command {
|
||||
sendTo = Config.ForwardChannels.Games
|
||||
done = true
|
||||
@@ -152,9 +172,7 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
if !done {
|
||||
cmdLen := len(cmd)
|
||||
|
||||
cmds := []string{"w ", "п ", "погода ", "погодка ", "погадка ", "weather "}
|
||||
|
||||
for _, command := range cmds {
|
||||
for _, command := range commandsWebappGoPrefix {
|
||||
if cmdLen > len(command) && cmd[0:len(command)] == command {
|
||||
sendTo = Config.ForwardChannels.WebappGo
|
||||
done = true
|
||||
@@ -168,10 +186,7 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
if !done {
|
||||
cmdLen := len(cmd)
|
||||
|
||||
cmds := []string{"karma ", "карма ", "rum ", "ром ", "vodka ", "водка ", "beer ", "пиво ", "tequila ",
|
||||
"текила ", "whisky ", "виски ", "absinthe ", "абсент "}
|
||||
|
||||
for _, command := range cmds {
|
||||
for _, command := range commandsPhrasesPrefix {
|
||||
if cmdLen > len(command) && cmd[0:len(command)] == command {
|
||||
sendTo = Config.ForwardChannels.Phrases
|
||||
|
||||
@@ -211,7 +226,7 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
message.Message = j.Message
|
||||
message.Plugin = j.Plugin
|
||||
message.Mode = j.Mode
|
||||
message.Misc.Fwdcnt = j.Misc.Fwdcnt
|
||||
message.Misc.Fwdcnt = *j.Misc.Fwdcnt
|
||||
message.Misc.Csign = j.Misc.Csign
|
||||
message.Misc.Username = j.Misc.Username
|
||||
message.Misc.Answer = j.Misc.Answer
|
||||
@@ -227,12 +242,26 @@ func MsgParser(ctx context.Context, msg string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Заталкиваем наш json в редиску.
|
||||
if err := RedisClient.Publish(ctx, sendTo, data).Err(); err != nil {
|
||||
log.Warnf("Unable to send data to redis channel %s: %s", sendTo, err)
|
||||
} else {
|
||||
log.Debugf("Send msg to redis channel %s: %s", sendTo, string(data))
|
||||
// Заталкиваем наш json в редиску с повторными попытками.
|
||||
var publishErr error
|
||||
|
||||
for attempt := 1; attempt <= retryAttempts; attempt++ {
|
||||
if publishErr = RedisClient.Publish(ctx, sendTo, data).Err(); publishErr == nil {
|
||||
log.Debugf("Send msg to redis channel %s: %s", sendTo, string(data))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnf("Unable to send data to redis channel %s (attempt %d/%d): %s",
|
||||
sendTo, attempt, retryAttempts, publishErr)
|
||||
|
||||
if attempt < retryAttempts {
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
log.Errorf("Failed to send data to redis channel %s after %d attempts: %s",
|
||||
sendTo, retryAttempts, publishErr)
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
|
||||
721
internal/misc/msgParser_test.go
Normal file
721
internal/misc/msgParser_test.go
Normal file
@@ -0,0 +1,721 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMsgParser_Validation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputJSON string
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "empty message should not panic",
|
||||
inputJSON: `{"from":"test","chatid":"1","userid":"1","message":"","plugin":"test","mode":"test"}`,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "short message less than csign",
|
||||
inputJSON: `{"from":"test","chatid":"1","userid":"1","message":"!","plugin":"test","mode":"test","misc":{"csign":"!!!"}}`,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "valid message with single char csign",
|
||||
inputJSON: `{"from":"test","chatid":"1","userid":"1","message":"!karma","plugin":"test","mode":"test","misc":{"csign":"!"}}`,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "message shorter than multi char csign",
|
||||
inputJSON: `{"from":"test","chatid":"1","userid":"1","message":"!","plugin":"test","mode":"test","misc":{"csign":"!!"}}`,
|
||||
expectPanic: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.expectPanic {
|
||||
t.Errorf("unexpected panic: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// We only test that parsing doesn't panic
|
||||
// Actual publishing requires Redis
|
||||
var msg rMsg
|
||||
if err := json.Unmarshal([]byte(tt.inputJSON), &msg); err != nil {
|
||||
t.Logf("JSON parse error (expected for some tests): %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_CommandRouting(t *testing.T) {
|
||||
// Save original config
|
||||
origConfig := Config
|
||||
Config = myConfig{
|
||||
Csign: "!",
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Games: "games",
|
||||
Phrases: "phrases",
|
||||
Webapp: "webapp",
|
||||
WebappGo: "webapp-go",
|
||||
Craniac: "craniac",
|
||||
},
|
||||
}
|
||||
defer func() { Config = origConfig }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
expected string
|
||||
}{
|
||||
// Phrases commands
|
||||
{"friday command", "!friday", "phrases"},
|
||||
{"fortune command", "!fortune", "phrases"},
|
||||
{"karma command", "!karma", "phrases"},
|
||||
{"russian friday", "!пятница", "phrases"},
|
||||
{"russian karma", "!карма", "phrases"},
|
||||
|
||||
// Webapp-go commands
|
||||
{"frog command", "!frog", "webapp-go"},
|
||||
{"cat command", "!cat", "webapp-go"},
|
||||
{"weather command", "!w Moskow", "webapp-go"},
|
||||
{"russian weather", "!погода Москва", "webapp-go"},
|
||||
|
||||
// Games commands
|
||||
{"dig command", "!dig", "games"},
|
||||
{"fish command", "!fish", "games"},
|
||||
{"russian dig", "!копать", "games"},
|
||||
|
||||
// Default fallback
|
||||
{"unknown command", "!unknown", "craniac"},
|
||||
{"no command prefix", "just some text", "craniac"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
msg := rMsg{
|
||||
From: "test",
|
||||
Chatid: "1",
|
||||
Userid: "1",
|
||||
Message: tt.message,
|
||||
Plugin: "test",
|
||||
Mode: "test",
|
||||
Misc: struct {
|
||||
Answer int64 `json:"answer,omitempty"`
|
||||
Botnick string `json:"bot_nick,omitempty"`
|
||||
Csign string `json:"csign,omitempty"`
|
||||
Fwdcnt *int64 `json:"fwd_cnt,omitempty"`
|
||||
GoodMorning int64 `json:"good_morning,omitempty"`
|
||||
Msgformat int64 `json:"msg_format,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
}{
|
||||
Csign: Config.Csign,
|
||||
Fwdcnt: new(int64),
|
||||
},
|
||||
}
|
||||
*msg.Misc.Fwdcnt = 1
|
||||
|
||||
result := determineChannel(msg.Message, msg.Misc.Csign)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_KarmaPatterns(t *testing.T) {
|
||||
origConfig := Config
|
||||
Config = myConfig{
|
||||
Csign: "!",
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Phrases: "phrases",
|
||||
Craniac: "craniac",
|
||||
},
|
||||
}
|
||||
defer func() { Config = origConfig }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
expected string
|
||||
}{
|
||||
{"karma increment", "something++", "phrases"},
|
||||
{"karma decrement", "something--", "phrases"},
|
||||
{"russian plus plus", "что-то++", "phrases"},
|
||||
{"no karma pattern", "just text", "craniac"},
|
||||
{"multiple lines no karma", "line1\nline2++", "craniac"},
|
||||
{"single line with karma", "line1++", "phrases"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := determineKarmaChannel(tt.message)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_FwdcntPointer(t *testing.T) {
|
||||
// Test that Fwdcnt pointer handling works correctly
|
||||
fwdcnt := new(int64)
|
||||
*fwdcnt = 0
|
||||
|
||||
if fwdcnt == nil {
|
||||
t.Error("fwdcnt should not be nil after new(int64)")
|
||||
}
|
||||
|
||||
if *fwdcnt != 0 {
|
||||
t.Errorf("expected 0, got %d", *fwdcnt)
|
||||
}
|
||||
|
||||
*fwdcnt++
|
||||
if *fwdcnt != 1 {
|
||||
t.Errorf("expected 1, got %d", *fwdcnt)
|
||||
}
|
||||
}
|
||||
|
||||
// determineChannel is extracted logic for testing command routing
|
||||
func determineChannel(message, csign string) string {
|
||||
sendTo := "craniac"
|
||||
|
||||
if len(message) >= len(csign) && message[0:len(csign)] == csign {
|
||||
cmd := message[len(csign):]
|
||||
|
||||
cmds := []string{"friday", "пятница", "proverb", "пословица", "пословиться", "fortune", "фортунка", "f", "ф",
|
||||
"karma", "карма", "rum", "ром", "vodka", "водка", "beer", "пиво", "tequila", "текила", "whisky", "виски",
|
||||
"absinthe", "абсент", "fuck"}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmd == command {
|
||||
return "phrases"
|
||||
}
|
||||
}
|
||||
|
||||
cmds = []string{"frog", "лягушка", "horse", "лошадь", "лошадка", "rabbit", "bunny", "кролик",
|
||||
"snail", "улитка", "cat", "кис", "fox", "лис", "buni", "anek", "анек", "анекдот",
|
||||
"xkcd", "monkeyuser", "tits", "boobs", "tities", "boobies", "сиси", "сисечки", "butt",
|
||||
"booty", "ass", "попа", "попка", "drink", "праздник", "owl", "сова", "сыч", "w", "п", "погода",
|
||||
"погодка", "погадка"}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmd == command {
|
||||
return "webapp-go"
|
||||
}
|
||||
}
|
||||
|
||||
cmds = []string{"dig", "копать", "fish", "fishing", "рыба", "рыбка", "рыбалка"}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmd == command {
|
||||
return "games"
|
||||
}
|
||||
}
|
||||
|
||||
cmdLen := len(cmd)
|
||||
cmds = []string{"w ", "п ", "погода ", "погодка ", "погадка ", "weather "}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmdLen > len(command) && cmd[0:len(command)] == command {
|
||||
return "webapp-go"
|
||||
}
|
||||
}
|
||||
|
||||
cmds = []string{"karma ", "карма ", "rum ", "ром ", "vodka ", "водка ", "beer ", "пиво ", "tequila ",
|
||||
"текила ", "whisky ", "виски ", "absinthe ", "абсент "}
|
||||
|
||||
for _, command := range cmds {
|
||||
if cmdLen > len(command) && cmd[0:len(command)] == command {
|
||||
return "phrases"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sendTo
|
||||
}
|
||||
|
||||
// determineKarmaChannel is extracted logic for testing karma patterns
|
||||
func determineKarmaChannel(message string) string {
|
||||
msgLen := len(message)
|
||||
|
||||
if msgLen > 2 {
|
||||
if message[msgLen-2:] == "--" || message[msgLen-2:] == "++" {
|
||||
// Предполагается, что менять карму мы будем для одной фразы, то есть для 1 строки
|
||||
if len(strings.Split(message, "\n")) == 1 {
|
||||
return "phrases"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "craniac"
|
||||
}
|
||||
|
||||
func TestMsgParser_BuildsMsgCorrectly(t *testing.T) {
|
||||
// Test that message building produces correct output
|
||||
fwdcnt := new(int64)
|
||||
*fwdcnt = 2
|
||||
|
||||
input := rMsg{
|
||||
From: "telegram",
|
||||
Chatid: "123",
|
||||
Userid: "456",
|
||||
Threadid: "789",
|
||||
Message: "!karma user",
|
||||
Plugin: "telegram",
|
||||
Mode: "private",
|
||||
Misc: struct {
|
||||
Answer int64 `json:"answer,omitempty"`
|
||||
Botnick string `json:"bot_nick,omitempty"`
|
||||
Csign string `json:"csign,omitempty"`
|
||||
Fwdcnt *int64 `json:"fwd_cnt,omitempty"`
|
||||
GoodMorning int64 `json:"good_morning,omitempty"`
|
||||
Msgformat int64 `json:"msg_format,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
}{
|
||||
Answer: 1,
|
||||
Botnick: "aleesa",
|
||||
Csign: "!",
|
||||
Fwdcnt: fwdcnt,
|
||||
Username: "testuser",
|
||||
},
|
||||
}
|
||||
|
||||
// Simulate the message building logic
|
||||
var message sMsg
|
||||
message.From = input.From
|
||||
message.Userid = input.Userid
|
||||
message.Chatid = input.Chatid
|
||||
message.Threadid = input.Threadid
|
||||
message.Message = input.Message
|
||||
message.Plugin = input.Plugin
|
||||
message.Mode = input.Mode
|
||||
message.Misc.Fwdcnt = *input.Misc.Fwdcnt
|
||||
message.Misc.Csign = input.Misc.Csign
|
||||
message.Misc.Username = input.Misc.Username
|
||||
message.Misc.Answer = input.Misc.Answer
|
||||
message.Misc.Botnick = input.Misc.Botnick
|
||||
message.Misc.Msgformat = input.Misc.Msgformat
|
||||
message.Misc.GoodMorning = input.Misc.GoodMorning
|
||||
|
||||
// Verify
|
||||
if message.Misc.Fwdcnt != 2 {
|
||||
t.Errorf("expected Fwdcnt 2, got %d", message.Misc.Fwdcnt)
|
||||
}
|
||||
if message.Misc.Answer != 1 {
|
||||
t.Errorf("expected Answer 1, got %d", message.Misc.Answer)
|
||||
}
|
||||
if message.Misc.Username != "testuser" {
|
||||
t.Errorf("expected Username 'testuser', got %s", message.Misc.Username)
|
||||
}
|
||||
|
||||
// Verify JSON serialization works
|
||||
data, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
// Verify we can unmarshal it back
|
||||
var result sMsg
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if result.From != message.From {
|
||||
t.Errorf("From mismatch: got %s", result.From)
|
||||
}
|
||||
if result.Misc.Fwdcnt != message.Misc.Fwdcnt {
|
||||
t.Errorf("Fwdcnt mismatch: got %d", result.Misc.Fwdcnt)
|
||||
}
|
||||
}
|
||||
|
||||
var _ = context.Background // for import
|
||||
|
||||
func TestMsgParser_MultiCharCsign(t *testing.T) {
|
||||
origConfig := Config
|
||||
Config = myConfig{
|
||||
Csign: "!!!",
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Games: "games",
|
||||
Phrases: "phrases",
|
||||
Webapp: "webapp",
|
||||
WebappGo: "webapp-go",
|
||||
Craniac: "craniac",
|
||||
},
|
||||
ForwardsMax: 5,
|
||||
}
|
||||
defer func() { Config = origConfig }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
csign string
|
||||
expected string
|
||||
}{
|
||||
{"double char csign !!", "!!karma", "!!", "phrases"},
|
||||
{"triple char csign !!!", "!!!friday", "!!!", "phrases"},
|
||||
{"slash csign /", "/help", "/", "craniac"},
|
||||
{"dot csign .", ".status", ".", "craniac"},
|
||||
{"message shorter than csign", "!", "!!", "craniac"},
|
||||
{"message equal to csign", "!!", "!!", "craniac"},
|
||||
{"message longer than csign", "!!!test", "!!!", "craniac"},
|
||||
{"russian with multi char csign", "!!!погода", "!!!", "webapp-go"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := determineChannel(tt.message, tt.csign)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_FwdcntLoopPrevention(t *testing.T) {
|
||||
origConfig := Config
|
||||
Config = myConfig{
|
||||
Csign: "!",
|
||||
ForwardsMax: 5,
|
||||
}
|
||||
defer func() { Config = origConfig }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fwdcnt int64
|
||||
expected bool // true = should be discarded
|
||||
}{
|
||||
{"fwdcnt 0 allowed", 0, false},
|
||||
{"fwdcnt 1 allowed", 1, false},
|
||||
{"fwdcnt 5 allowed", 5, false},
|
||||
{"fwdcnt 6 discarded", 6, true},
|
||||
{"fwdcnt 10 discarded", 10, true},
|
||||
{"fwdcnt 100 discarded", 100, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
shouldDiscard := tt.fwdcnt > Config.ForwardsMax
|
||||
if shouldDiscard != tt.expected {
|
||||
t.Errorf("fwdcnt=%d: expected discard=%v, got %v", tt.fwdcnt, tt.expected, shouldDiscard)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_DefaultCsignFromConfig(t *testing.T) {
|
||||
origConfig := Config
|
||||
Config = myConfig{
|
||||
Csign: "!",
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Phrases: "phrases",
|
||||
},
|
||||
}
|
||||
defer func() { Config = origConfig }()
|
||||
|
||||
// When csign is empty in message, it should use Config.Csign
|
||||
message := "!karma"
|
||||
csign := ""
|
||||
|
||||
if csign == "" {
|
||||
csign = Config.Csign
|
||||
}
|
||||
|
||||
result := determineChannel(message, csign)
|
||||
if result != "phrases" {
|
||||
t.Errorf("expected phrases, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_KarmaAnswerFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command string
|
||||
expectAnswer bool
|
||||
}{
|
||||
{"karma without args sets answer", "karma", true},
|
||||
// Note: "karma user" (with arg) doesn't match "karma " pattern, so Answer is NOT set
|
||||
{"karma with space and arg", "karma user", false},
|
||||
{"russian karma without args", "карма", true},
|
||||
// Note: Russian karma with args requires different handling due to UTF-8 encoding
|
||||
{"friday no answer", "friday", false},
|
||||
{"fortune no answer", "fortune", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Simulate the karma answer logic (checks for exact match)
|
||||
isExactKarma := tt.command == "karma" || tt.command == "карма"
|
||||
shouldSetAnswer := isExactKarma
|
||||
|
||||
if shouldSetAnswer != tt.expectAnswer {
|
||||
t.Errorf("command=%s: expected answer=%v, got %v", tt.command, tt.expectAnswer, shouldSetAnswer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_EmptyCommandAfterCsign(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
csign string
|
||||
expected string
|
||||
}{
|
||||
{"just csign", "!", "!", "craniac"},
|
||||
{"csign with space", "! ", "!", "craniac"},
|
||||
{"double csign", "!!", "!!", "craniac"},
|
||||
{"double csign with space", "!! ", "!!", "craniac"},
|
||||
{"slash with space", "/ ", "/", "craniac"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := determineChannel(tt.message, tt.csign)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_MoreRussianCommands(t *testing.T) {
|
||||
origConfig := Config
|
||||
Config = myConfig{
|
||||
Csign: "!",
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Games: "games",
|
||||
Phrases: "phrases",
|
||||
WebappGo: "webapp-go",
|
||||
},
|
||||
}
|
||||
defer func() { Config = origConfig }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
expected string
|
||||
}{
|
||||
// Russian weather commands
|
||||
{"russian pogoda", "!погода", "webapp-go"},
|
||||
{"russian pogodka", "!погодка", "webapp-go"},
|
||||
{"russian pogadka", "!погадка", "webapp-go"},
|
||||
{"russian pogoda with city", "!погода Москва", "webapp-go"},
|
||||
|
||||
// Russian games commands
|
||||
{"russian kopat", "!копать", "games"},
|
||||
{"russian ryba", "!рыба", "games"},
|
||||
{"russian ribka", "!рыбка", "games"},
|
||||
{"russian rybalka", "!рыбалка", "games"},
|
||||
|
||||
// Russian phrases commands
|
||||
{"russian friday", "!пятница", "phrases"},
|
||||
{"russian proverb", "!пословица", "phrases"},
|
||||
{"russian fortune", "!фортунка", "phrases"},
|
||||
{"russian f", "!ф", "phrases"},
|
||||
|
||||
// More Russian webapp
|
||||
{"russian frog", "!лягушка", "webapp-go"},
|
||||
{"russian horse", "!лошадь", "webapp-go"},
|
||||
{"russian rabbit", "!кролик", "webapp-go"},
|
||||
{"russian snail", "!улитка", "webapp-go"},
|
||||
{"russian fox", "!лис", "webapp-go"},
|
||||
{"russian anek", "!анек", "webapp-go"},
|
||||
{"russian drink", "!праздник", "webapp-go"},
|
||||
{"russian owl", "!сова", "webapp-go"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := determineChannel(tt.message, Config.Csign)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_MoreEnglishCommands(t *testing.T) {
|
||||
origConfig := Config
|
||||
Config = myConfig{
|
||||
Csign: "!",
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Games: "games",
|
||||
Phrases: "phrases",
|
||||
WebappGo: "webapp-go",
|
||||
},
|
||||
}
|
||||
defer func() { Config = origConfig }()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
expected string
|
||||
}{
|
||||
// More phrases commands
|
||||
{"proverb", "!proverb", "phrases"},
|
||||
{"poslovitsa", "!пословица", "phrases"},
|
||||
{"poslovitsya", "!пословиться", "phrases"},
|
||||
{"rum", "!rum", "phrases"},
|
||||
{"vodka", "!vodka", "phrases"},
|
||||
{"beer", "!beer", "phrases"},
|
||||
{"tequila", "!tequila", "phrases"},
|
||||
{"whisky", "!whisky", "phrases"},
|
||||
{"absinthe", "!absinthe", "phrases"},
|
||||
{"fuck", "!fuck", "phrases"},
|
||||
{"f short", "!f", "phrases"},
|
||||
|
||||
// More webapp-go commands
|
||||
{"horse", "!horse", "webapp-go"},
|
||||
{"rabbit", "!rabbit", "webapp-go"},
|
||||
{"bunny", "!bunny", "webapp-go"},
|
||||
{"snail", "!snail", "webapp-go"},
|
||||
{"fox", "!fox", "webapp-go"},
|
||||
{"buni", "!buni", "webapp-go"},
|
||||
{"anek", "!anek", "webapp-go"},
|
||||
{"xkcd", "!xkcd", "webapp-go"},
|
||||
{"monkeyuser", "!monkeyuser", "webapp-go"},
|
||||
{"drink", "!drink", "webapp-go"},
|
||||
{"owl", "!owl", "webapp-go"},
|
||||
{"w short", "!w", "webapp-go"},
|
||||
|
||||
// More games commands
|
||||
{"fishing", "!fishing", "games"},
|
||||
|
||||
// Phrases with arguments
|
||||
{"karma with user", "!karma username", "phrases"},
|
||||
{"rum with drink", "!rum cola", "phrases"},
|
||||
{"vodka with juice", "!водка сок", "phrases"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := determineChannel(tt.message, Config.Csign)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_KarmaInlinePattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
expected string
|
||||
}{
|
||||
{"simple increment", "user++", "phrases"},
|
||||
{"simple decrement", "user--", "phrases"},
|
||||
{"with spaces", "some text++", "phrases"},
|
||||
{"cyrillic increment", "пользователь++", "phrases"},
|
||||
{"cyrillic decrement", "пользователь--", "phrases"},
|
||||
{"emoji", "👍++", "phrases"},
|
||||
{"underscore", "test_name++", "phrases"},
|
||||
{"dash in name", "test-name++", "phrases"},
|
||||
{"not karma - just plus", "a+b", "craniac"},
|
||||
// "a+++" ends with "++", so it's treated as karma increment
|
||||
{"multiple plus ends with ++", "a+++", "phrases"},
|
||||
{"not karma - embedded", "a++b", "craniac"},
|
||||
{"decrement edge", "a--b", "craniac"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := determineKarmaChannel(tt.message)
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %s, got %s", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryConstants(t *testing.T) {
|
||||
if retryAttempts != 3 {
|
||||
t.Errorf("expected retryAttempts=3, got %d", retryAttempts)
|
||||
}
|
||||
if retryDelay != time.Second {
|
||||
t.Errorf("expected retryDelay=1s, got %v", retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgParser_MessageValidationLogic(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonStr string
|
||||
expectValid bool // true if message has all required fields
|
||||
}{
|
||||
// Valid messages
|
||||
{"valid minimal", `{"from":"t","chatid":"1","userid":"1","message":"m","plugin":"p","mode":"m"}`, true},
|
||||
{"valid full", `{"from":"t","chatid":"1","userid":"1","message":"m","plugin":"p","mode":"m","misc":{"csign":"!","fwd_cnt":1}}`, true},
|
||||
|
||||
// Missing required fields - they unmarshal but are empty strings
|
||||
{"missing from", `{"chatid":"1","userid":"1","message":"m","plugin":"p","mode":"m"}`, false},
|
||||
{"missing chatid", `{"from":"t","userid":"1","message":"m","plugin":"p","mode":"m"}`, false},
|
||||
{"missing userid", `{"from":"t","chatid":"1","message":"m","plugin":"p","mode":"m"}`, false},
|
||||
{"missing message", `{"from":"t","chatid":"1","userid":"1","plugin":"p","mode":"m"}`, false},
|
||||
{"missing plugin", `{"from":"t","chatid":"1","userid":"1","message":"m","mode":"m"}`, false},
|
||||
{"missing mode", `{"from":"t","chatid":"1","userid":"1","message":"m","plugin":"p"}`, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var msg rMsg
|
||||
err := json.Unmarshal([]byte(tt.jsonStr), &msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected JSON error: %v", err)
|
||||
}
|
||||
|
||||
// Check if message has all required fields (the validation logic in MsgParser)
|
||||
isValid := msg.From != "" && msg.Chatid != "" && msg.Userid != "" &&
|
||||
msg.Message != "" && msg.Plugin != "" && msg.Mode != ""
|
||||
|
||||
if isValid != tt.expectValid {
|
||||
t.Errorf("valid=%v, expected %v", isValid, tt.expectValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,8 @@ func ReadConfig() {
|
||||
|
||||
if sampleConfig.Channel == "" {
|
||||
log.Errorf("Channel field in config file %s must be set", location)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Частичная проверка, ровно то, куда мы _точно_ щепрввляем сообщения исходя из бизнес-логики приложения
|
||||
@@ -132,6 +134,8 @@ func ReadConfig() {
|
||||
|
||||
if sampleConfig.Csign == "" {
|
||||
log.Errorf("Csign field in config file %s must be set", location)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if sampleConfig.ForwardsMax == 0 {
|
||||
|
||||
327
internal/misc/readConfig_test.go
Normal file
327
internal/misc/readConfig_test.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfigValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config myConfig
|
||||
expectedServer string
|
||||
expectedPort int
|
||||
expectValid bool
|
||||
}{
|
||||
{
|
||||
name: "full valid config",
|
||||
config: myConfig{
|
||||
Server: "redis.example.com",
|
||||
Port: 6380,
|
||||
Channel: "test-channel",
|
||||
Csign: "/",
|
||||
},
|
||||
expectedServer: "redis.example.com",
|
||||
expectedPort: 6380,
|
||||
expectValid: true,
|
||||
},
|
||||
{
|
||||
name: "config with defaults",
|
||||
config: myConfig{
|
||||
Channel: "test-channel",
|
||||
Csign: "!",
|
||||
},
|
||||
expectedServer: "localhost",
|
||||
expectedPort: 6379,
|
||||
expectValid: true,
|
||||
},
|
||||
{
|
||||
name: "missing channel",
|
||||
config: myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Csign: "!",
|
||||
},
|
||||
expectValid: false,
|
||||
},
|
||||
{
|
||||
name: "missing csign",
|
||||
config: myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Channel: "test",
|
||||
},
|
||||
expectValid: false,
|
||||
},
|
||||
{
|
||||
name: "zero port uses default",
|
||||
config: myConfig{
|
||||
Server: "localhost",
|
||||
Port: 0,
|
||||
Channel: "test",
|
||||
Csign: "!",
|
||||
},
|
||||
expectedServer: "localhost",
|
||||
expectedPort: 6379,
|
||||
expectValid: true,
|
||||
},
|
||||
{
|
||||
name: "zero timeout uses default",
|
||||
config: myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Timeout: 0,
|
||||
Channel: "test",
|
||||
Csign: "!",
|
||||
},
|
||||
expectedServer: "localhost",
|
||||
expectedPort: 6379,
|
||||
expectValid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := tt.config
|
||||
valid := validateConfig(&cfg)
|
||||
|
||||
if valid != tt.expectValid {
|
||||
t.Errorf("expected valid=%v, got %v", tt.expectValid, valid)
|
||||
}
|
||||
|
||||
if tt.expectValid {
|
||||
applyDefaults(&cfg)
|
||||
if cfg.Server != tt.expectedServer {
|
||||
t.Errorf("expected Server=%s, got %s", tt.expectedServer, cfg.Server)
|
||||
}
|
||||
if cfg.Port != tt.expectedPort {
|
||||
t.Errorf("expected Port=%d, got %d", tt.expectedPort, cfg.Port)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardChannelsDefaults(t *testing.T) {
|
||||
cfg := myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Channel: "test",
|
||||
Csign: "!",
|
||||
}
|
||||
|
||||
applyDefaults(&cfg)
|
||||
|
||||
if cfg.ForwardChannels.Games != "games" {
|
||||
t.Errorf("expected games='games', got '%s'", cfg.ForwardChannels.Games)
|
||||
}
|
||||
if cfg.ForwardChannels.Phrases != "phrases" {
|
||||
t.Errorf("expected phrases='phrases', got '%s'", cfg.ForwardChannels.Phrases)
|
||||
}
|
||||
if cfg.ForwardChannels.Webapp != "webapp" {
|
||||
t.Errorf("expected webapp='webapp', got '%s'", cfg.ForwardChannels.Webapp)
|
||||
}
|
||||
if cfg.ForwardChannels.WebappGo != "webapp-go" {
|
||||
t.Errorf("expected webapp-go='webapp-go', got '%s'", cfg.ForwardChannels.WebappGo)
|
||||
}
|
||||
if cfg.ForwardChannels.Craniac != "craniac" {
|
||||
t.Errorf("expected craniac='craniac', got '%s'", cfg.ForwardChannels.Craniac)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardChannelsCustomValues(t *testing.T) {
|
||||
cfg := myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Channel: "test",
|
||||
Csign: "!",
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Games: "my-games",
|
||||
Phrases: "my-phrases",
|
||||
Webapp: "my-webapp",
|
||||
WebappGo: "my-webapp-go",
|
||||
Craniac: "my-craniac",
|
||||
},
|
||||
}
|
||||
|
||||
applyDefaults(&cfg)
|
||||
|
||||
if cfg.ForwardChannels.Games != "my-games" {
|
||||
t.Errorf("expected games='my-games', got '%s'", cfg.ForwardChannels.Games)
|
||||
}
|
||||
if cfg.ForwardChannels.Phrases != "my-phrases" {
|
||||
t.Errorf("expected phrases='my-phrases', got '%s'", cfg.ForwardChannels.Phrases)
|
||||
}
|
||||
}
|
||||
|
||||
// validateConfig simulates the validation logic from ReadConfig
|
||||
func validateConfig(cfg *myConfig) bool {
|
||||
if cfg.Channel == "" {
|
||||
return false
|
||||
}
|
||||
if cfg.Csign == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// applyDefaults simulates the default application logic from ReadConfig
|
||||
func applyDefaults(cfg *myConfig) {
|
||||
if cfg.Server == "" {
|
||||
cfg.Server = "localhost"
|
||||
}
|
||||
if cfg.Port == 0 {
|
||||
cfg.Port = 6379
|
||||
}
|
||||
if cfg.Timeout == 0 {
|
||||
cfg.Timeout = 10
|
||||
}
|
||||
if cfg.Loglevel == "" {
|
||||
cfg.Loglevel = "info"
|
||||
}
|
||||
if cfg.ForwardChannels.Games == "" {
|
||||
cfg.ForwardChannels.Games = "games"
|
||||
}
|
||||
if cfg.ForwardChannels.Phrases == "" {
|
||||
cfg.ForwardChannels.Phrases = "phrases"
|
||||
}
|
||||
if cfg.ForwardChannels.Webapp == "" {
|
||||
cfg.ForwardChannels.Webapp = "webapp"
|
||||
}
|
||||
if cfg.ForwardChannels.WebappGo == "" {
|
||||
cfg.ForwardChannels.WebappGo = "webapp-go"
|
||||
}
|
||||
if cfg.ForwardChannels.Craniac == "" {
|
||||
cfg.ForwardChannels.Craniac = "craniac"
|
||||
}
|
||||
if cfg.Csign == "" {
|
||||
cfg.Csign = "!"
|
||||
}
|
||||
if cfg.ForwardsMax == 0 {
|
||||
cfg.ForwardsMax = 5
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoglevelValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
loglevel string
|
||||
expectedOK bool
|
||||
}{
|
||||
{"valid error", "error", true},
|
||||
{"valid warn", "warn", true},
|
||||
{"valid info", "info", true},
|
||||
{"valid debug", "debug", true},
|
||||
{"invalid level", "trace", false},
|
||||
{"empty uses default", "", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := &myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Channel: "test",
|
||||
Csign: "!",
|
||||
}
|
||||
|
||||
if tt.loglevel != "" {
|
||||
cfg.Loglevel = tt.loglevel
|
||||
}
|
||||
|
||||
applyDefaults(cfg)
|
||||
|
||||
// Valid loglevels should result in a non-empty loglevel
|
||||
// Invalid ones would still get set to "info" as default
|
||||
if cfg.Loglevel == "" && tt.expectedOK {
|
||||
t.Error("expected non-empty loglevel")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardsMaxDefault(t *testing.T) {
|
||||
cfg := myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Channel: "test",
|
||||
Csign: "!",
|
||||
}
|
||||
|
||||
applyDefaults(&cfg)
|
||||
|
||||
if cfg.ForwardsMax != 5 {
|
||||
t.Errorf("expected ForwardsMax=5, got %d", cfg.ForwardsMax)
|
||||
}
|
||||
|
||||
cfg.ForwardsMax = 10
|
||||
applyDefaults(&cfg)
|
||||
|
||||
if cfg.ForwardsMax != 10 {
|
||||
t.Errorf("expected ForwardsMax=10, got %d", cfg.ForwardsMax)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigJSONRoundtrip(t *testing.T) {
|
||||
original := myConfig{
|
||||
Server: "redis.example.com",
|
||||
Port: 6380,
|
||||
Timeout: 15,
|
||||
Loglevel: "debug",
|
||||
Log: "/var/log/test.log",
|
||||
Channel: "test-channel",
|
||||
Csign: "/",
|
||||
ForwardsMax: 10,
|
||||
ForwardChannels: struct {
|
||||
Games string `json:"games,omitempty"`
|
||||
Phrases string `json:"phrases,omitempty"`
|
||||
Webapp string `json:"webapp,omitempty"`
|
||||
WebappGo string `json:"webapp-go,omitempty"`
|
||||
Craniac string `json:"craniac,omitempty"`
|
||||
}{
|
||||
Games: "custom-games",
|
||||
Phrases: "custom-phrases",
|
||||
Webapp: "custom-webapp",
|
||||
WebappGo: "custom-webapp-go",
|
||||
Craniac: "custom-craniac",
|
||||
},
|
||||
}
|
||||
|
||||
// Marshal
|
||||
data, err := json.Marshal(original)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal
|
||||
var restored myConfig
|
||||
if err := json.Unmarshal(data, &restored); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
// Compare
|
||||
if restored.Server != original.Server {
|
||||
t.Errorf("Server: expected %s, got %s", original.Server, restored.Server)
|
||||
}
|
||||
if restored.Port != original.Port {
|
||||
t.Errorf("Port: expected %d, got %d", original.Port, restored.Port)
|
||||
}
|
||||
if restored.Channel != original.Channel {
|
||||
t.Errorf("Channel: expected %s, got %s", original.Channel, restored.Channel)
|
||||
}
|
||||
if restored.Csign != original.Csign {
|
||||
t.Errorf("Csign: expected %s, got %s", original.Csign, restored.Csign)
|
||||
}
|
||||
if restored.ForwardsMax != original.ForwardsMax {
|
||||
t.Errorf("ForwardsMax: expected %d, got %d", original.ForwardsMax, restored.ForwardsMax)
|
||||
}
|
||||
if restored.ForwardChannels.Games != original.ForwardChannels.Games {
|
||||
t.Errorf("ForwardChannels.Games: expected %s, got %s", original.ForwardChannels.Games, restored.ForwardChannels.Games)
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,11 @@ func SigHandler() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Чтобы не срать в логи ошибками от редиски, проставим shutdown state приложения в true.
|
||||
Shutdown = true
|
||||
// Отменяем контекст редиски, чтобы все ожидающие операции завершились.
|
||||
CancelRedisCtx()
|
||||
|
||||
// Отпишемся от всех каналов и закроем коннект к редиске.
|
||||
if err = Subscriber.Unsubscribe(Ctx); err != nil {
|
||||
if err = Subscriber.Unsubscribe(RedisCtx); err != nil {
|
||||
log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -52,11 +52,11 @@ type rMsg struct {
|
||||
Answer int64 `json:"answer,omitempty"`
|
||||
Botnick string `json:"bot_nick,omitempty"`
|
||||
Csign string `json:"csign,omitempty"`
|
||||
Fwdcnt int64 `json:"fwd_cnt,omitempty"`
|
||||
Fwdcnt *int64 `json:"fwd_cnt,omitempty"`
|
||||
GoodMorning int64 `json:"good_morning,omitempty"`
|
||||
Msgformat int64 `json:"msg_format,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
} `json:"Misc"`
|
||||
} `json:"misc"`
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
|
||||
348
internal/misc/types_test.go
Normal file
348
internal/misc/types_test.go
Normal file
@@ -0,0 +1,348 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRMsgJSONDeserialization(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonInput string
|
||||
expectedFrom string
|
||||
expectedChatid string
|
||||
expectedPlugin string
|
||||
expectCsignNil bool
|
||||
}{
|
||||
{
|
||||
name: "full message with all fields",
|
||||
jsonInput: `{"from":"telegram","chatid":"123","userid":"456","message":"hello","plugin":"telegram","mode":"private","misc":{"answer":1,"bot_nick":"aleesa","csign":"!","fwd_cnt":1,"good_morning":0,"msg_format":1,"username":"user"}}`,
|
||||
expectedFrom: "telegram",
|
||||
expectedChatid: "123",
|
||||
expectedPlugin: "telegram",
|
||||
expectCsignNil: false,
|
||||
},
|
||||
{
|
||||
name: "minimal message",
|
||||
jsonInput: `{"from":"irc","chatid":"#room","userid":"nick","message":"!karma","plugin":"irc","mode":"channel"}`,
|
||||
expectedFrom: "irc",
|
||||
expectedChatid: "#room",
|
||||
expectedPlugin: "irc",
|
||||
expectCsignNil: true,
|
||||
},
|
||||
{
|
||||
name: "message with fwd_cnt zero",
|
||||
jsonInput: `{"from":"jabber","chatid":"room@conference","userid":"user","message":"test","plugin":"jabber","mode":"group","misc":{"fwd_cnt":0}}`,
|
||||
expectedFrom: "jabber",
|
||||
expectedChatid: "room@conference",
|
||||
expectedPlugin: "jabber",
|
||||
expectCsignNil: true,
|
||||
},
|
||||
{
|
||||
name: "message with threadid",
|
||||
jsonInput: `{"from":"telegram","chatid":"123","userid":"456","threadid":"789","message":"reply","plugin":"telegram","mode":"thread"}`,
|
||||
expectedFrom: "telegram",
|
||||
expectedChatid: "123",
|
||||
expectedPlugin: "telegram",
|
||||
expectCsignNil: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var msg rMsg
|
||||
if err := json.Unmarshal([]byte(tt.jsonInput), &msg); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if msg.From != tt.expectedFrom {
|
||||
t.Errorf("From: expected %s, got %s", tt.expectedFrom, msg.From)
|
||||
}
|
||||
if msg.Chatid != tt.expectedChatid {
|
||||
t.Errorf("Chatid: expected %s, got %s", tt.expectedChatid, msg.Chatid)
|
||||
}
|
||||
if msg.Plugin != tt.expectedPlugin {
|
||||
t.Errorf("Plugin: expected %s, got %s", tt.expectedPlugin, msg.Plugin)
|
||||
}
|
||||
if tt.expectCsignNil && msg.Misc.Csign != "" {
|
||||
t.Errorf("Csign: expected empty, got %s", msg.Misc.Csign)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSMsgJSONSerialization(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
message sMsg
|
||||
}{
|
||||
{
|
||||
name: "full message",
|
||||
message: sMsg{
|
||||
From: "telegram",
|
||||
Chatid: "123",
|
||||
Userid: "456",
|
||||
Threadid: "789",
|
||||
Message: "Hello world",
|
||||
Plugin: "telegram",
|
||||
Mode: "private",
|
||||
Misc: struct {
|
||||
Answer int64 `json:"answer"`
|
||||
Botnick string `json:"bot_nick"`
|
||||
Csign string `json:"csign"`
|
||||
Fwdcnt int64 `json:"fwd_cnt"`
|
||||
GoodMorning int64 `json:"good_morning"`
|
||||
Msgformat int64 `json:"msg_format"`
|
||||
Username string `json:"username"`
|
||||
}{
|
||||
Answer: 1,
|
||||
Botnick: "aleesa",
|
||||
Csign: "!",
|
||||
Fwdcnt: 2,
|
||||
GoodMorning: 0,
|
||||
Msgformat: 1,
|
||||
Username: "testuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "minimal message",
|
||||
message: sMsg{
|
||||
From: "irc",
|
||||
Chatid: "#room",
|
||||
Userid: "nick",
|
||||
Message: "!help",
|
||||
Plugin: "irc",
|
||||
Mode: "channel",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data, err := json.Marshal(tt.message)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's valid JSON
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
t.Fatalf("marshaled data is not valid JSON: %v", err)
|
||||
}
|
||||
|
||||
// Verify key fields are present
|
||||
if _, ok := result["from"]; !ok {
|
||||
t.Error("missing 'from' field")
|
||||
}
|
||||
if _, ok := result["chatid"]; !ok {
|
||||
t.Error("missing 'chatid' field")
|
||||
}
|
||||
if _, ok := result["message"]; !ok {
|
||||
t.Error("missing 'message' field")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRMsgToSMsgConversion(t *testing.T) {
|
||||
fwdcnt := new(int64)
|
||||
*fwdcnt = 3
|
||||
|
||||
input := rMsg{
|
||||
From: "telegram",
|
||||
Chatid: "123",
|
||||
Userid: "456",
|
||||
Threadid: "789",
|
||||
Message: "!friday",
|
||||
Plugin: "telegram",
|
||||
Mode: "private",
|
||||
Misc: struct {
|
||||
Answer int64 `json:"answer,omitempty"`
|
||||
Botnick string `json:"bot_nick,omitempty"`
|
||||
Csign string `json:"csign,omitempty"`
|
||||
Fwdcnt *int64 `json:"fwd_cnt,omitempty"`
|
||||
GoodMorning int64 `json:"good_morning,omitempty"`
|
||||
Msgformat int64 `json:"msg_format,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
}{
|
||||
Answer: 0,
|
||||
Botnick: "aleesa",
|
||||
Csign: "!",
|
||||
Fwdcnt: fwdcnt,
|
||||
GoodMorning: 1,
|
||||
Msgformat: 1,
|
||||
Username: "testuser",
|
||||
},
|
||||
}
|
||||
|
||||
// Convert
|
||||
output := convertToSMsg(input)
|
||||
|
||||
// Verify all fields transferred
|
||||
if output.From != input.From {
|
||||
t.Errorf("From: expected %s, got %s", input.From, output.From)
|
||||
}
|
||||
if output.Chatid != input.Chatid {
|
||||
t.Errorf("Chatid: expected %s, got %s", input.Chatid, output.Chatid)
|
||||
}
|
||||
if output.Userid != input.Userid {
|
||||
t.Errorf("Userid: expected %s, got %s", input.Userid, output.Userid)
|
||||
}
|
||||
if output.Threadid != input.Threadid {
|
||||
t.Errorf("Threadid: expected %s, got %s", input.Threadid, output.Threadid)
|
||||
}
|
||||
if output.Message != input.Message {
|
||||
t.Errorf("Message: expected %s, got %s", input.Message, output.Message)
|
||||
}
|
||||
if output.Plugin != input.Plugin {
|
||||
t.Errorf("Plugin: expected %s, got %s", input.Plugin, output.Plugin)
|
||||
}
|
||||
if output.Mode != input.Mode {
|
||||
t.Errorf("Mode: expected %s, got %s", input.Mode, output.Mode)
|
||||
}
|
||||
if output.Misc.Csign != input.Misc.Csign {
|
||||
t.Errorf("Misc.Csign: expected %s, got %s", input.Misc.Csign, output.Misc.Csign)
|
||||
}
|
||||
if output.Misc.Fwdcnt != *input.Misc.Fwdcnt {
|
||||
t.Errorf("Misc.Fwdcnt: expected %d, got %d", *input.Misc.Fwdcnt, output.Misc.Fwdcnt)
|
||||
}
|
||||
if output.Misc.Username != input.Misc.Username {
|
||||
t.Errorf("Misc.Username: expected %s, got %s", input.Misc.Username, output.Misc.Username)
|
||||
}
|
||||
if output.Misc.Answer != input.Misc.Answer {
|
||||
t.Errorf("Misc.Answer: expected %d, got %d", input.Misc.Answer, output.Misc.Answer)
|
||||
}
|
||||
if output.Misc.Botnick != input.Misc.Botnick {
|
||||
t.Errorf("Misc.Botnick: expected %s, got %s", input.Misc.Botnick, output.Misc.Botnick)
|
||||
}
|
||||
if output.Misc.Msgformat != input.Misc.Msgformat {
|
||||
t.Errorf("Misc.Msgformat: expected %d, got %d", input.Misc.Msgformat, output.Misc.Msgformat)
|
||||
}
|
||||
if output.Misc.GoodMorning != input.Misc.GoodMorning {
|
||||
t.Errorf("Misc.GoodMorning: expected %d, got %d", input.Misc.GoodMorning, output.Misc.GoodMorning)
|
||||
}
|
||||
}
|
||||
|
||||
// convertToSMsg simulates the conversion logic from msgParser.go
|
||||
func convertToSMsg(j rMsg) sMsg {
|
||||
var message sMsg
|
||||
message.From = j.From
|
||||
message.Userid = j.Userid
|
||||
message.Chatid = j.Chatid
|
||||
message.Threadid = j.Threadid
|
||||
message.Message = j.Message
|
||||
message.Plugin = j.Plugin
|
||||
message.Mode = j.Mode
|
||||
message.Misc.Fwdcnt = *j.Misc.Fwdcnt
|
||||
message.Misc.Csign = j.Misc.Csign
|
||||
message.Misc.Username = j.Misc.Username
|
||||
message.Misc.Answer = j.Misc.Answer
|
||||
message.Misc.Botnick = j.Misc.Botnick
|
||||
message.Misc.Msgformat = j.Misc.Msgformat
|
||||
message.Misc.GoodMorning = j.Misc.GoodMorning
|
||||
return message
|
||||
}
|
||||
|
||||
func TestJSONFieldNaming(t *testing.T) {
|
||||
// Test that sMsg uses lowercase "misc" in JSON
|
||||
msg := sMsg{
|
||||
From: "test",
|
||||
Chatid: "1",
|
||||
Userid: "2",
|
||||
Message: "hello",
|
||||
Plugin: "test",
|
||||
Mode: "test",
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
// Check for lowercase "misc"
|
||||
if !contains(string(data), `"misc"`) {
|
||||
t.Errorf("expected lowercase 'misc' in JSON, got: %s", string(data))
|
||||
}
|
||||
|
||||
// Ensure it doesn't contain capitalized "Misc"
|
||||
if contains(string(data), `"Misc"`) {
|
||||
t.Errorf("should not contain capitalized 'Misc' in JSON, got: %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsAt(s, substr))
|
||||
}
|
||||
|
||||
func containsAt(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestRMsgFwdcntPointer(t *testing.T) {
|
||||
// Test that Fwdcnt as pointer works correctly with JSON
|
||||
jsonInput := `{"from":"test","chatid":"1","userid":"1","message":"test","plugin":"test","mode":"test","misc":{"fwd_cnt":5}}`
|
||||
|
||||
var msg rMsg
|
||||
if err := json.Unmarshal([]byte(jsonInput), &msg); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if msg.Misc.Fwdcnt == nil {
|
||||
t.Fatal("Fwdcnt should not be nil")
|
||||
}
|
||||
|
||||
if *msg.Misc.Fwdcnt != 5 {
|
||||
t.Errorf("expected Fwdcnt=5, got %d", *msg.Misc.Fwdcnt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRMsgFwdcntNilWhenOmitted(t *testing.T) {
|
||||
jsonInput := `{"from":"test","chatid":"1","userid":"1","message":"test","plugin":"test","mode":"test"}`
|
||||
|
||||
var msg rMsg
|
||||
if err := json.Unmarshal([]byte(jsonInput), &msg); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if msg.Misc.Fwdcnt != nil {
|
||||
t.Errorf("Fwdcnt should be nil when omitted, got %d", *msg.Misc.Fwdcnt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMyConfigForwardChannels(t *testing.T) {
|
||||
cfg := myConfig{
|
||||
Server: "localhost",
|
||||
Port: 6379,
|
||||
Channel: "test",
|
||||
Csign: "!",
|
||||
}
|
||||
|
||||
// Test that struct can hold custom values
|
||||
cfg.ForwardChannels.Games = "custom-games"
|
||||
cfg.ForwardChannels.Phrases = "custom-phrases"
|
||||
cfg.ForwardChannels.Webapp = "custom-webapp"
|
||||
cfg.ForwardChannels.WebappGo = "custom-webapp-go"
|
||||
cfg.ForwardChannels.Craniac = "custom-craniac"
|
||||
|
||||
if cfg.ForwardChannels.Games != "custom-games" {
|
||||
t.Errorf("Games: expected custom-games, got %s", cfg.ForwardChannels.Games)
|
||||
}
|
||||
if cfg.ForwardChannels.Phrases != "custom-phrases" {
|
||||
t.Errorf("Phrases: expected custom-phrases, got %s", cfg.ForwardChannels.Phrases)
|
||||
}
|
||||
if cfg.ForwardChannels.Webapp != "custom-webapp" {
|
||||
t.Errorf("Webapp: expected custom-webapp, got %s", cfg.ForwardChannels.Webapp)
|
||||
}
|
||||
if cfg.ForwardChannels.WebappGo != "custom-webapp-go" {
|
||||
t.Errorf("WebappGo: expected custom-webapp-go, got %s", cfg.ForwardChannels.WebappGo)
|
||||
}
|
||||
if cfg.ForwardChannels.Craniac != "custom-craniac" {
|
||||
t.Errorf("Craniac: expected custom-craniac, got %s", cfg.ForwardChannels.Craniac)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user