(new): expand runtime APIs
(fix): harden concurrent lifecycle (tests): add regression coverage (doc): update v1.2 guidance
This commit is contained in:
@@ -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]{
|
||||
|
||||
Reference in New Issue
Block a user