(new): expand runtime APIs
Golang lint / lint (push) Successful in 58s
Golang lint / lint (pull_request) Successful in 13m10s

(fix): harden concurrent lifecycle
(tests): add regression coverage
(doc): update v1.2 guidance
This commit is contained in:
2026-08-20 11:08:45 +03:00
parent 29b208eeec
commit 24040fe164
37 changed files with 1129 additions and 157 deletions
+73
View File
@@ -7,6 +7,7 @@ import (
"net/http"
"strings"
"testing"
"time"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/sneklog/v2"
@@ -1301,6 +1302,8 @@ func TestParseCommandTable(t *testing.T) {
{name: "plain text", text: "hello", wantPrefix: "", wantCmd: "", wantArgs: ""},
{name: "command no args", text: "/start", wantPrefix: "/", wantCmd: "start", wantArgs: ""},
{name: "command with args", text: "/ban 42 reason", wantPrefix: "/", wantCmd: "ban", wantArgs: "42 reason"},
{name: "tab separator", text: "/ban\t42", wantPrefix: "/", wantCmd: "ban", wantArgs: "42"},
{name: "newline separator", text: "/ban\n42", wantPrefix: "/", wantCmd: "ban", wantArgs: "42"},
{name: "alternate prefix", text: "!ping", wantPrefix: "!", wantCmd: "ping", wantArgs: ""},
{name: "leading space after prefix", text: "/ start now", wantPrefix: "/", wantCmd: "start", wantArgs: "now"},
{name: "command with botname", text: "/start@mybot extra", wantPrefix: "/", wantCmd: "start@mybot", wantArgs: "extra"},
@@ -1332,6 +1335,7 @@ func TestHandleMessageStripsBotUsernameSuffix(t *testing.T) {
}{
{name: "matching botname", botUsername: "mybot", text: "/start@mybot hello", wantCalled: true},
{name: "matching botname no args", botUsername: "mybot", text: "/start@mybot", wantCalled: true},
{name: "matching botname ignores case", botUsername: "MyBot", text: "/start@mybot", wantCalled: true},
{name: "other botname", botUsername: "mybot", text: "/start@otherbot hello", wantCalled: false},
{name: "no botname", botUsername: "mybot", text: "/start hello", wantCalled: true},
{name: "bot has no username", botUsername: "", text: "/start@mybot hello", wantCalled: false},
@@ -1581,6 +1585,75 @@ func sceneMessageUpdate(updateID int, text string) tgapi.Update {
}
}
func TestHandleDoesNotSerializeUnrelatedUsersWithoutScene(t *testing.T) {
started := make(chan struct{}, 2)
release := make(chan struct{})
plugin := NewPlugin[NoData]("commands")
plugin.Command("work", func(*MessageContext, NoData) error {
started <- struct{}{}
<-release
return nil
})
bot := &Bot[NoData]{
logger: sneklog.NewLogger(),
plugins: []Plugin[NoData]{clonePlugin(plugin)},
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
}
defer func() { _ = bot.logger.Close() }()
updateFor := func(id int) tgapi.Update {
return tgapi.Update{UpdateID: id, Type: tgapi.UpdateTypeMessage, Message: &tgapi.Message{
MessageID: id, Text: "/work", From: &tgapi.User{ID: int64(id)},
Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypeSupergroup},
}}
}
done := make(chan struct{}, 2)
for id := 1; id <= 2; id++ {
update := updateFor(id)
go func() {
bot.handle(context.Background(), &update)
done <- struct{}{}
}()
}
for range 2 {
select {
case <-started:
case <-time.After(time.Second):
close(release)
t.Fatal("updates from unrelated users in one chat were serialized")
}
}
close(release)
<-done
<-done
}
func TestCommandMiddlewareBlockIsNotReportedAsError(t *testing.T) {
called := false
plugin := NewPlugin[NoData]("commands")
plugin.Command("blocked", func(*MessageContext, NoData) error {
called = true
return nil
}).Use(NewMiddleware("deny", func(*MessageContext, NoData) bool { return false }))
observer := &recordingObserver{}
bot := &Bot[NoData]{
logger: sneklog.NewLogger(), plugins: []Plugin[NoData]{clonePlugin(plugin)},
prefixes: []string{"/"}, observer: observer, sessionStore: NewMemorySessionStore(),
}
defer func() { _ = bot.logger.Close() }()
update := sceneMessageUpdate(201, "/blocked")
bot.handle(context.Background(), &update)
if called {
t.Fatal("command executed after middleware blocked it")
}
if len(observer.errors) != 0 {
t.Fatalf("middleware block emitted errors: %#v", observer.errors)
}
}
func TestHandleCallbackObserverEmitsDecodeErrors(t *testing.T) {
observer := &recordingObserver{}
bot := &Bot[NoData]{