mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-10-03 07:13:24 +00:00
Correct comments that described the retired webhook transport, a removed /cron route, the old KV store and wrapper types, and behaviour that has since changed; drop plan and review labels; reword two startup log lines that overstated or misnamed what they report.
171 lines
5.8 KiB
Go
171 lines
5.8 KiB
Go
package alias_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/go-telegram/bot"
|
|
"github.com/go-telegram/bot/models"
|
|
|
|
"github.com/tiennm99/miti99bot/internal/modules"
|
|
"github.com/tiennm99/miti99bot/internal/modules/alias"
|
|
"github.com/tiennm99/miti99bot/internal/storage"
|
|
"github.com/tiennm99/miti99bot/internal/testutil"
|
|
)
|
|
|
|
// The headline of the fallback: a saved name becomes its own command.
|
|
func TestFallback_SavedNameWorksAsItsOwnCommand(t *testing.T) {
|
|
rb := installAlias(t)
|
|
rb.Bot.ProcessUpdate(context.Background(),
|
|
aliasCmd("cheer", &models.Message{Sticker: &models.Sticker{FileID: "sticker-id"}}))
|
|
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/cheer"))
|
|
|
|
call, ok := callTo(rb, "sendSticker")
|
|
if !ok {
|
|
t.Fatalf("no sendSticker call; /cheer did not resolve: %+v", rb.Sent())
|
|
}
|
|
if got := call.Form["sticker"]; got != "sticker-id" {
|
|
t.Errorf("sticker = %q, want the saved file_id", got)
|
|
}
|
|
}
|
|
|
|
// Groups send /cmd@botname, and the fallback must normalise that the same way
|
|
// the command matcher does.
|
|
func TestFallback_ToleratesAtBotnameSuffix(t *testing.T) {
|
|
rb := installAlias(t)
|
|
rb.Bot.ProcessUpdate(context.Background(),
|
|
aliasCmd("cheer", &models.Message{Text: "yay"}))
|
|
|
|
rb.Reset()
|
|
// The fixture builder stops the entity at '@', but real Telegram includes
|
|
// the whole "/cheer@miti99bot" in it — which is the case the stripping
|
|
// exists for, so the entity is widened here to match the wire format.
|
|
upd := testutil.NewPrivateMessage(7, "/cheer@miti99bot")
|
|
upd.Message.Entities[0].Length = len(upd.Message.Text)
|
|
rb.Bot.ProcessUpdate(context.Background(), upd)
|
|
|
|
if got := rb.LastSent().Text(); got != "yay" {
|
|
t.Errorf("reply = %q, want the alias to resolve despite the @suffix", got)
|
|
}
|
|
}
|
|
|
|
// Mobile keyboards autocapitalise; /Cheer must reach the same alias.
|
|
func TestFallback_IsCaseInsensitive(t *testing.T) {
|
|
rb := installAlias(t)
|
|
rb.Bot.ProcessUpdate(context.Background(),
|
|
aliasCmd("cheer", &models.Message{Text: "yay"}))
|
|
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/CHEER"))
|
|
|
|
if got := rb.LastSent().Text(); got != "yay" {
|
|
t.Errorf("reply = %q, want case-folded resolution", got)
|
|
}
|
|
}
|
|
|
|
// Silence on a miss is deliberate: the fallback sees every unrecognised command
|
|
// in every chat, so replying would turn typos into noise and would confirm
|
|
// which names are taken.
|
|
func TestFallback_UnknownNameIsSilent(t *testing.T) {
|
|
rb := installAlias(t)
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/pign"))
|
|
|
|
if calls := rb.Sent(); len(calls) != 0 {
|
|
t.Errorf("unknown command produced output: %+v", calls)
|
|
}
|
|
}
|
|
|
|
// The rule the user asked for: behaviour in code wins over an alias, always.
|
|
// Registered commands are installed before the fallback, and the bot library
|
|
// returns the first matching handler.
|
|
func TestFallback_NeverShadowsARegisteredCommand(t *testing.T) {
|
|
var realRan bool
|
|
realModule := func(_ modules.Deps) modules.Module {
|
|
return modules.Module{Commands: []modules.Command{{
|
|
Name: "cheer",
|
|
Visibility: modules.VisibilityPublic,
|
|
Description: "the real thing",
|
|
Handler: func(_ context.Context, _ *bot.Bot, _ *models.Update) error {
|
|
realRan = true
|
|
return nil
|
|
},
|
|
}}}
|
|
}
|
|
|
|
rb := testutil.NewRecordingBot(t)
|
|
reg, err := modules.Build([]string{"alias", "real"},
|
|
map[string]modules.Factory{"alias": alias.New, "real": realModule},
|
|
storage.NewMemoryProvider(), modules.BuildOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Build: %v", err)
|
|
}
|
|
modules.Install(rb.Bot, reg, modules.Auth{})
|
|
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/cheer"))
|
|
if !realRan {
|
|
t.Error("the registered /cheer handler did not run")
|
|
}
|
|
}
|
|
|
|
// Refusing at assignment time is the companion to dispatch order: an alias
|
|
// named after a real command would only ever be reachable via /insert.
|
|
func TestAlias_RefusesNameOfARegisteredCommand(t *testing.T) {
|
|
rb := installAlias(t)
|
|
|
|
// /aliases is one of this module's own commands.
|
|
rb.Bot.ProcessUpdate(context.Background(),
|
|
aliasCmd("aliases", &models.Message{Text: "hijack"}))
|
|
|
|
rb.AssertSentText(t, "already a command of mine")
|
|
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/insert aliases"))
|
|
rb.AssertSentText(t, "Nothing is saved")
|
|
}
|
|
|
|
func TestUnalias_DeletesAndThenNameIsFree(t *testing.T) {
|
|
rb := installAlias(t)
|
|
rb.Bot.ProcessUpdate(context.Background(),
|
|
aliasCmd("temp", &models.Message{Text: "content"}))
|
|
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/unalias temp"))
|
|
rb.AssertSentText(t, "Deleted <code>temp</code>")
|
|
|
|
// Gone from /insert, from the list, and from the bare-command path.
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/insert temp"))
|
|
rb.AssertSentText(t, "Nothing is saved")
|
|
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/aliases"))
|
|
rb.AssertSentText(t, "No aliases saved yet")
|
|
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/temp"))
|
|
if calls := rb.Sent(); len(calls) != 0 {
|
|
t.Errorf("deleted alias still answered as a command: %+v", calls)
|
|
}
|
|
}
|
|
|
|
func TestUnalias_UnknownNameIsReported(t *testing.T) {
|
|
rb := installAlias(t)
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(7, "/unalias ghost"))
|
|
|
|
rb.AssertSentText(t, "Nothing is saved")
|
|
}
|
|
|
|
// Deleting is open to anyone, matching the overwrite rule — the namespace is
|
|
// shared, so the permission model is too.
|
|
func TestUnalias_AnyoneMayDelete(t *testing.T) {
|
|
rb := installAlias(t)
|
|
rb.Bot.ProcessUpdate(context.Background(),
|
|
aliasCmd("shared", &models.Message{Text: "content"}))
|
|
|
|
rb.Reset()
|
|
rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(99, "/unalias shared"))
|
|
rb.AssertSentText(t, "Deleted")
|
|
}
|