(new): rich message support
(fix): runtime reliability (tests): regression coverage (doc): v1.1 release notes
This commit is contained in:
+141
@@ -3,7 +3,10 @@ package laniakea
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
|
||||
"git.scuroneko.dev/scuroneko/sneklog/v2"
|
||||
@@ -41,6 +44,114 @@ func TestPluginAddSceneRegistersScene(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotAddPluginsSkipsDuplicateSceneNames(t *testing.T) {
|
||||
first := NewPlugin[NoData]("first")
|
||||
first.Scene("shared")
|
||||
second := NewPlugin[NoData]("second")
|
||||
second.Scene("shared")
|
||||
|
||||
bot := &Bot[NoData]{logger: sneklog.NewLogger()}
|
||||
bot.AddPlugins(first, second)
|
||||
|
||||
if _, ok := bot.plugins[0].scenes["shared"]; !ok {
|
||||
t.Fatal("first registered scene was removed")
|
||||
}
|
||||
if _, ok := bot.plugins[1].scenes["shared"]; ok {
|
||||
t.Fatal("duplicate scene was registered")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSceneUpdatesForSameSessionAreSerialized(t *testing.T) {
|
||||
entered := make(chan int, 2)
|
||||
releaseFirst := make(chan struct{})
|
||||
var calls atomic.Int64
|
||||
|
||||
plugin := NewPlugin[NoData]("wizard")
|
||||
plugin.Scene("counter").
|
||||
SetEntry("start").
|
||||
OnStep("start", func(ctx *SceneContext, db NoData) (SceneResult, error) {
|
||||
var value int
|
||||
if err := ctx.BindData(&value); err != nil {
|
||||
return SceneResult{}, err
|
||||
}
|
||||
call := int(calls.Add(1))
|
||||
entered <- call
|
||||
if call == 1 {
|
||||
<-releaseFirst
|
||||
}
|
||||
value++
|
||||
if err := ctx.SaveData(value); err != nil {
|
||||
return SceneResult{}, err
|
||||
}
|
||||
return ctx.Stay(), nil
|
||||
})
|
||||
|
||||
bot := &Bot[NoData]{
|
||||
logger: sneklog.NewLogger(),
|
||||
prefixes: []string{"/"},
|
||||
sessionStore: NewMemorySessionStore(),
|
||||
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
|
||||
}
|
||||
bot.AddPlugins(plugin)
|
||||
key := "user_id:42:chat_id:100"
|
||||
session := SceneSession{Scene: "counter", Step: "start"}
|
||||
if err := session.SaveData(0); err != nil {
|
||||
t.Fatalf("SaveData returned error: %v", err)
|
||||
}
|
||||
if err := bot.sessionStore.Set(key, session); err != nil {
|
||||
t.Fatalf("Set returned error: %v", err)
|
||||
}
|
||||
|
||||
update := func(id int) *tgapi.Update {
|
||||
return &tgapi.Update{
|
||||
UpdateID: id,
|
||||
Type: tgapi.UpdateTypeMessage,
|
||||
Message: &tgapi.Message{
|
||||
MessageID: id,
|
||||
Text: "increment",
|
||||
Chat: &tgapi.Chat{ID: 100, Type: tgapi.ChatTypePrivate},
|
||||
From: &tgapi.User{ID: 42},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
bot.handle(context.Background(), update(1))
|
||||
}()
|
||||
if got := <-entered; got != 1 {
|
||||
t.Fatalf("first handler call = %d, want 1", got)
|
||||
}
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
bot.handle(context.Background(), update(2))
|
||||
}()
|
||||
select {
|
||||
case call := <-entered:
|
||||
t.Fatalf("second handler entered before first completed: call %d", call)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
close(releaseFirst)
|
||||
if got := <-entered; got != 2 {
|
||||
t.Fatalf("second handler call = %d, want 2", got)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
got, err := bot.sessionStore.Get(key)
|
||||
if err != nil {
|
||||
t.Fatalf("Get returned error: %v", err)
|
||||
}
|
||||
var value int
|
||||
if err := got.BindData(&value); err != nil {
|
||||
t.Fatalf("BindData returned error: %v", err)
|
||||
}
|
||||
if value != 2 {
|
||||
t.Fatalf("session value = %d, want 2", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotAddPluginsPreservesScenesAndHandlesThem(t *testing.T) {
|
||||
called := false
|
||||
|
||||
@@ -836,6 +947,36 @@ func TestSceneMessageFallbackRunsWhenNoCommandOrStepMatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemorySessionStoreDoesNotAliasSessionData(t *testing.T) {
|
||||
store := NewMemorySessionStore()
|
||||
input := []byte("initial")
|
||||
session := SceneSession{Scene: "signup"}
|
||||
session.SetData(input)
|
||||
|
||||
input[0] = 'X'
|
||||
if got := string(session.GetData()); got != "initial" {
|
||||
t.Fatalf("SetData retained caller slice: got %q", got)
|
||||
}
|
||||
if err := store.Set("user:1", session); err != nil {
|
||||
t.Fatalf("Set returned error: %v", err)
|
||||
}
|
||||
|
||||
first, err := store.Get("user:1")
|
||||
if err != nil {
|
||||
t.Fatalf("Get returned error: %v", err)
|
||||
}
|
||||
data := first.GetData()
|
||||
data[0] = 'X'
|
||||
|
||||
second, err := store.Get("user:1")
|
||||
if err != nil {
|
||||
t.Fatalf("second Get returned error: %v", err)
|
||||
}
|
||||
if got := string(second.GetData()); got != "initial" {
|
||||
t.Fatalf("Get exposed stored data for mutation: got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSceneSessionSupportsUserScopeWithoutMessage(t *testing.T) {
|
||||
bot := &Bot[NoData]{
|
||||
logger: sneklog.NewLogger(),
|
||||
|
||||
Reference in New Issue
Block a user