mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-10-05 22:14:29 +00:00
/blacklist is the short form of the two read commands: bare it lists both lists like /blacklist_rules, and given text it judges that text like /blacklist_check. Both long names stay for when the intent should be explicit. An argument of only whitespace is not an argument, so it lists. /whitelist_rnd returns one whitelist entry chosen at random, and says the list is empty rather than answering with nothing. It reads only the whitelist, and only for the calling topic. The six existing command descriptions are shortened alongside. /help renders as one un-chunked message pinned at 4096 runes, and two more commands pushed it to 4123; the shorter wording brings it to 4049. That ceiling is a shared limit this module did not create, and it will bind again on the next command added anywhere in the repo.
162 lines
5.5 KiB
Go
162 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/tiennm99/miti99bot/internal/modules"
|
|
"github.com/tiennm99/miti99bot/internal/modules/stock"
|
|
"github.com/tiennm99/miti99bot/internal/storage"
|
|
"github.com/tiennm99/miti99bot/internal/systemstate"
|
|
)
|
|
|
|
func TestResolveCommitSHA(t *testing.T) {
|
|
prev := gitSHA
|
|
defer func() { gitSHA = prev }()
|
|
|
|
// Coolify runtime env wins over the baked fallback.
|
|
gitSHA = "baked"
|
|
if got := resolveCommitSHA(" runtime-sha "); got != "runtime-sha" {
|
|
t.Errorf("env present: got %q, want trimmed runtime-sha", got)
|
|
}
|
|
|
|
// Empty env falls back to the VCS revision embedded by go build.
|
|
if got := resolveCommitSHA(""); got != "baked" {
|
|
t.Errorf("env empty: got %q, want baked fallback", got)
|
|
}
|
|
|
|
// Neither source set → "unknown" so the owner still gets the startup DM.
|
|
gitSHA = ""
|
|
if got := resolveCommitSHA(" "); got != "unknown" {
|
|
t.Errorf("both empty: got %q, want unknown", got)
|
|
}
|
|
}
|
|
|
|
func TestInitStockStoreRunsStartupMigration(t *testing.T) {
|
|
ctx := context.Background()
|
|
provider := storage.NewMemoryProvider()
|
|
if err := initStockStore(ctx, provider); err != nil {
|
|
t.Fatalf("initStockStore: %v", err)
|
|
}
|
|
marker, exists, err := systemstate.New(provider.Collection(systemstate.CollectionName)).Get(ctx, "migration:stock-dividend-history-v1")
|
|
if err != nil || !exists || marker.Status != "completed" {
|
|
t.Fatalf("marker=%+v exists=%v err=%v", marker, exists, err)
|
|
}
|
|
if _, _, err := storage.Typed[stock.Portfolio](provider.Collection(stock.CollectionName)).Get(ctx, "user:1"); !errors.Is(err, storage.ErrNotFound) {
|
|
t.Fatalf("unexpected portfolio lookup error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestInitStatsStoreRunsStartupMigration(t *testing.T) {
|
|
ctx := context.Background()
|
|
provider := storage.NewMemoryProvider()
|
|
if err := initStatsStore(ctx, provider); err != nil {
|
|
t.Fatalf("initStatsStore: %v", err)
|
|
}
|
|
marker, exists, err := systemstate.New(provider.Collection(systemstate.CollectionName)).Get(ctx, "migration:stats-delete-stock-dividend-v1")
|
|
if err != nil || !exists || marker.Status != "completed" {
|
|
t.Fatalf("marker=%+v exists=%v err=%v", marker, exists, err)
|
|
}
|
|
}
|
|
|
|
func TestInitStockStorePropagatesMigrationError(t *testing.T) {
|
|
want := errors.New("migration failed")
|
|
err := initStockStoreWith(context.Background(), storage.NewMemoryProvider(), func(context.Context, storage.Collection, storage.Collection) error {
|
|
return want
|
|
})
|
|
if !errors.Is(err, want) {
|
|
t.Fatalf("initStockStoreWith error=%v, want %v", err, want)
|
|
}
|
|
}
|
|
|
|
func TestInitStatsStorePropagatesMigrationError(t *testing.T) {
|
|
want := errors.New("migration failed")
|
|
err := initStatsStoreWith(context.Background(), storage.NewMemoryProvider(), func(context.Context, storage.Collection, storage.Collection) error {
|
|
return want
|
|
})
|
|
if !errors.Is(err, want) {
|
|
t.Fatalf("initStatsStoreWith error=%v, want %v", err, want)
|
|
}
|
|
}
|
|
|
|
func TestShortCommitSHA(t *testing.T) {
|
|
if got := shortCommitSHA(" 0123456789abcdef "); got != "0123456" {
|
|
t.Errorf("full revision: got %q, want %q", got, "0123456")
|
|
}
|
|
if got := shortCommitSHA("abc123"); got != "abc123" {
|
|
t.Errorf("short revision: got %q, want %q", got, "abc123")
|
|
}
|
|
}
|
|
|
|
func TestComposeDoesNotOverrideSourceCommit(t *testing.T) {
|
|
b, err := os.ReadFile(filepath.Join("..", "..", "compose.yml"))
|
|
if err != nil {
|
|
t.Fatalf("read compose.yml: %v", err)
|
|
}
|
|
for _, line := range strings.Split(string(b), "\n") {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(trimmed, "SOURCE_COMMIT:") || strings.HasPrefix(trimmed, "- SOURCE_COMMIT") {
|
|
t.Fatalf("compose.yml must not declare SOURCE_COMMIT; Coolify supplies it at runtime and an explicit Compose value can override it with empty")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFactoriesIncludesExpectedModules(t *testing.T) {
|
|
catalog := factories()
|
|
for _, name := range []string{"gold", "coin"} {
|
|
if catalog[name] == nil {
|
|
t.Fatalf("factories missing %s", name)
|
|
}
|
|
}
|
|
reg, err := modules.Build([]string{"gold", "coin"}, catalog, storage.NewMemoryProvider(), modules.BuildOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Build selected modules: %v", err)
|
|
}
|
|
for _, name := range []string{
|
|
"gold_price", "gold_topup", "gold_buy", "gold_sell", "gold_portfolio",
|
|
"coin_price", "coin_topup", "coin_buy", "coin_sell", "coin_portfolio",
|
|
} {
|
|
if _, ok := reg.AllCommands[name]; !ok {
|
|
t.Fatalf("missing command %s", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFactoriesRegistersBlacklistCommands(t *testing.T) {
|
|
catalog := factories()
|
|
if catalog["blacklist"] == nil {
|
|
t.Fatal("factories missing blacklist")
|
|
}
|
|
reg, err := modules.Build([]string{"blacklist"}, catalog, storage.NewMemoryProvider(), modules.BuildOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Build blacklist: %v", err)
|
|
}
|
|
for _, name := range []string{
|
|
"blacklist", "blacklist_add", "blacklist_del", "blacklist_rules",
|
|
"blacklist_check", "whitelist_add", "whitelist_del", "whitelist_rnd",
|
|
} {
|
|
if _, ok := reg.AllCommands[name]; !ok {
|
|
t.Fatalf("missing command %s", name)
|
|
}
|
|
}
|
|
if got := len(reg.AllCommands); got != 8 {
|
|
t.Fatalf("blacklist registered %d commands, want 8", got)
|
|
}
|
|
}
|
|
|
|
// An empty MODULES loads every module, so a command name that collides with an
|
|
// existing module surfaces here as a test failure rather than as a startup
|
|
// crash on deploy.
|
|
func TestFactoriesBuildWholeCatalog(t *testing.T) {
|
|
if _, err := modules.Build(nil, factories(), storage.NewMemoryProvider(), modules.BuildOptions{}); err != nil {
|
|
t.Fatalf("Build whole catalog: %v", err)
|
|
}
|
|
}
|