0
Files
aleesa-misc-go/internal/misc/msgParser_test.go
2026-03-30 17:18:51 +03:00

722 lines
20 KiB
Go

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)
}
})
}
}