Files
miti99bot/internal/modules/misc/misc_test.go
T
tiennm99 3e8d6bf2a7 refactor(storage): replace KVStore with generic typed DocStore[T]
Delete the byte-oriented KVStore/VersionedStore abstraction and the
DynamoDB/memory KV backends. Add a generic typed store (DocStore[T] with
Provider/Collection/Typed) persisting each value as a flattened native
Mongo document (storedDoc[T] via bson inline) — no value envelope.

- MongoDB is the only runtime backend; memory kept for tests/local.
- All modules + deploynotify use typed stores; persisted structs carry
  bson tags == json names (incl. nested lolschedule/wordle types).
- lolschedule wraps its array/scalar values in named structs.
- migrate-dynamo-to-mongo writes the flattened shape via Typed[bson.M]
  with wrap rules; Scan/--dry-run/--verify retained.

Verified: go vet/build clean; full go test green hermetically and
in-container vs real Mongo 7 + DynamoDB Local (storage integration +
migrator e2e).
2026-06-28 18:02:11 +07:00

76 lines
2.2 KiB
Go

package misc
import (
"context"
"testing"
"github.com/tiennm99/miti99bot/internal/modules"
"github.com/tiennm99/miti99bot/internal/storage"
)
// We test the per-command store behaviour directly — the bot/Telegram side is
// thin (single SendMessage) and exercising it would require a fake bot HTTP
// server. The store interaction is the part with logic worth locking down.
// newMiscStore returns a fresh in-memory typed lastPing store for tests.
func newMiscStore() storage.DocStore[lastPing] {
return storage.Typed[lastPing](storage.NewMemoryProvider().Collection("misc"))
}
func TestNew_RegistersExpectedCommands(t *testing.T) {
deps := modules.Deps{Store: storage.NewMemoryProvider().Collection("misc")}
mod := New(deps)
want := map[string]modules.Visibility{
"ping": modules.VisibilityPublic,
"mstats": modules.VisibilityProtected,
"fortytwo": modules.VisibilityPrivate,
"trongtruonghop": modules.VisibilityPublic,
}
if len(mod.Commands) != len(want) {
t.Fatalf("commands count = %d, want %d", len(mod.Commands), len(want))
}
for _, c := range mod.Commands {
v, ok := want[c.Name]
if !ok {
t.Errorf("unexpected command %q", c.Name)
continue
}
if c.Visibility != v {
t.Errorf("command %q visibility = %d, want %d", c.Name, c.Visibility, v)
}
if c.Handler == nil {
t.Errorf("command %q has nil handler", c.Name)
}
}
}
func TestPing_WritesLastPingStore(t *testing.T) {
ctx := context.Background()
store := newMiscStore()
// Drive the store side directly: lock the wire format as a ms-epoch number
// (not an RFC3339 string), matching every other timestamp in the bot's store.
if err := store.Put(ctx, lastPingKey, lastPing{At: 1700000000000}); err != nil {
t.Fatalf("Put: %v", err)
}
got, _, err := store.Get(ctx, lastPingKey)
if err != nil {
t.Fatalf("Get: %v", err)
}
if got.At != 1700000000000 {
t.Errorf("ms-epoch round-trip: At = %d, want 1700000000000", got.At)
}
}
func TestMstats_MissingKeyReturnsErrNotFound(t *testing.T) {
ctx := context.Background()
store := newMiscStore()
_, _, err := store.Get(ctx, lastPingKey)
if err != storage.ErrNotFound {
t.Errorf("Get missing = %v, want ErrNotFound", err)
}
}