mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-11 03:13:46 +00:00
/noitu now posts the group play card that /noitupvp used to, and the solo game against the bot moves to /noitubot. In a private chat /noitu points the user to a group, or to /noitubot. /noitutop ranks a group's players across all of its cards by wins, best game and games played. A one-time startup migration moves /stats history to the new names in order: noitu to noitubot first, then noitupvp to noitu.
149 lines
5.4 KiB
Go
149 lines
5.4 KiB
Go
package stats
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/tiennm99/tiennm99bot/internal/storage"
|
|
"github.com/tiennm99/tiennm99bot/internal/systemstate"
|
|
)
|
|
|
|
func TestInitStore_SwapsNoituCommandStats(t *testing.T) {
|
|
assertNoituCommandSwap(t, storage.NewMemoryProvider())
|
|
}
|
|
|
|
// assertNoituCommandSwap seeds the solo game's history under "noitu" and the
|
|
// group game's under "noitupvp", runs the startup migration twice, and checks
|
|
// that the solo history landed on "noitubot" and the group history on
|
|
// "noitu" without the two mixing.
|
|
func assertNoituCommandSwap(t *testing.T, provider storage.Provider) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
statsColl := provider.Collection("stats")
|
|
systemColl := provider.Collection(systemstate.CollectionName)
|
|
docs := storage.Typed[usageEntry](statsColl)
|
|
|
|
seeds := map[string]usageEntry{
|
|
usageKey("noitu", 0): {Cmd: "noitu", N: 5},
|
|
usageKey("noitu", 7): {Cmd: "noitu", UserID: 7, Username: "alice", N: 2},
|
|
usageKey("noitupvp", 0): {Cmd: "noitupvp", N: 3},
|
|
usageKey("noitupvp", 7): {Cmd: "noitupvp", UserID: 7, Username: "alice", N: 4},
|
|
usageKey("noitupvp", 8): {Cmd: "noitupvp", UserID: 8, Username: "bob", N: 6},
|
|
// A command that only shares the prefix stays where it is.
|
|
usageKey("noitutop", 7): {Cmd: "noitutop", UserID: 7, Username: "alice", N: 1},
|
|
}
|
|
for key, entry := range seeds {
|
|
if err := docs.Put(ctx, key, entry); err != nil {
|
|
t.Fatalf("seed %s: %v", key, err)
|
|
}
|
|
}
|
|
|
|
for run := range 2 {
|
|
if err := InitStore(ctx, statsColl, systemColl); err != nil {
|
|
t.Fatalf("InitStore run %d: %v", run+1, err)
|
|
}
|
|
}
|
|
|
|
want := map[string]usageEntry{
|
|
usageKey("noitubot", 0): {Cmd: "noitubot", N: 5},
|
|
usageKey("noitubot", 7): {Cmd: "noitubot", UserID: 7, Username: "alice", N: 2},
|
|
usageKey("noitu", 0): {Cmd: "noitu", N: 3},
|
|
usageKey("noitu", 7): {Cmd: "noitu", UserID: 7, Username: "alice", N: 4},
|
|
usageKey("noitu", 8): {Cmd: "noitu", UserID: 8, Username: "bob", N: 6},
|
|
usageKey("noitutop", 7): {Cmd: "noitutop", UserID: 7, Username: "alice", N: 1},
|
|
}
|
|
for key, w := range want {
|
|
got, _, err := docs.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("get %s: %v", key, err)
|
|
}
|
|
if got != w {
|
|
t.Errorf("%s = %+v, want %+v", key, got, w)
|
|
}
|
|
}
|
|
for _, key := range []string{usageKey("noitupvp", 0), usageKey("noitupvp", 7), usageKey("noitupvp", 8)} {
|
|
if _, _, err := docs.Get(ctx, key); !errors.Is(err, storage.ErrNotFound) {
|
|
t.Errorf("old key %s err = %v, want ErrNotFound", key, err)
|
|
}
|
|
}
|
|
|
|
state := systemstate.New(systemColl)
|
|
for key, count := range map[string]int64{
|
|
"stats:command-rename:noitu-to-noitubot": 7,
|
|
"stats:command-rename:noitupvp-to-noitu": 13,
|
|
} {
|
|
rec, ok, err := state.Get(ctx, key)
|
|
if err != nil || !ok || rec.Status != "complete" || rec.Count != count {
|
|
t.Errorf("marker %s = %+v ok=%v err=%v, want complete count %d", key, rec, ok, err, count)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A boot that stopped after the first rename resumes with the second only:
|
|
// the rows already under the new "noitu" name are group history and must not
|
|
// move to noitubot again.
|
|
func TestInitStore_NoituSwapResumesAfterFirstStep(t *testing.T) {
|
|
ctx := context.Background()
|
|
provider := storage.NewMemoryProvider()
|
|
statsColl := provider.Collection("stats")
|
|
systemColl := provider.Collection(systemstate.CollectionName)
|
|
docs := storage.Typed[usageEntry](statsColl)
|
|
|
|
if err := migrateCommandRename(ctx, statsColl, systemColl, commandRenames[0]); err != nil {
|
|
t.Fatalf("first step: %v", err)
|
|
}
|
|
for key, entry := range map[string]usageEntry{
|
|
usageKey("noitubot", 7): {Cmd: "noitubot", UserID: 7, Username: "alice", N: 2},
|
|
// Moved by an interrupted second step before the restart.
|
|
usageKey("noitu", 7): {Cmd: "noitu", UserID: 7, Username: "alice", N: 4},
|
|
usageKey("noitupvp", 8): {Cmd: "noitupvp", UserID: 8, Username: "bob", N: 6},
|
|
} {
|
|
if err := docs.Put(ctx, key, entry); err != nil {
|
|
t.Fatalf("seed %s: %v", key, err)
|
|
}
|
|
}
|
|
|
|
if err := InitStore(ctx, statsColl, systemColl); err != nil {
|
|
t.Fatalf("InitStore: %v", err)
|
|
}
|
|
for key, n := range map[string]int64{
|
|
usageKey("noitubot", 7): 2,
|
|
usageKey("noitu", 7): 4,
|
|
usageKey("noitu", 8): 6,
|
|
} {
|
|
got, _, err := docs.Get(ctx, key)
|
|
if err != nil || got.N != n {
|
|
t.Errorf("%s = %+v err=%v, want n %d", key, got, err, n)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A rename merges into rows already under the new name and keeps a row that
|
|
// was only ever deleted history marked deleted.
|
|
func TestMigrateCommandRename_MergesAndKeepsDeletedFlag(t *testing.T) {
|
|
ctx := context.Background()
|
|
provider := storage.NewMemoryProvider()
|
|
statsColl := provider.Collection("stats")
|
|
systemColl := provider.Collection(systemstate.CollectionName)
|
|
docs := storage.Typed[usageEntry](statsColl)
|
|
for key, entry := range map[string]usageEntry{
|
|
usageKey("old", 0): {Cmd: "old", N: 2, Deleted: true},
|
|
usageKey("old", 7): {Cmd: "old", UserID: 7, Username: "alice", N: 3},
|
|
usageKey("new", 7): {Cmd: "new", UserID: 7, Username: "al", N: 5},
|
|
} {
|
|
if err := docs.Put(ctx, key, entry); err != nil {
|
|
t.Fatalf("seed %s: %v", key, err)
|
|
}
|
|
}
|
|
if err := migrateCommandRename(ctx, statsColl, systemColl, commandRename{oldCmd: "old", newCmd: "new", markerKey: "test-rename"}); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
if got, _, _ := docs.Get(ctx, usageKey("new", 0)); got != (usageEntry{Cmd: "new", N: 2, Deleted: true}) {
|
|
t.Errorf("anonymous = %+v", got)
|
|
}
|
|
if got, _, _ := docs.Get(ctx, usageKey("new", 7)); got != (usageEntry{Cmd: "new", UserID: 7, Username: "alice", N: 8}) {
|
|
t.Errorf("user = %+v", got)
|
|
}
|
|
}
|