Files
miti99bot/cmd/migrate-dynamo-to-mongo/encode_test.go
T
tiennm99 b81b6501c5 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

88 lines
2.9 KiB
Go

package main
import (
"testing"
"go.mongodb.org/mongo-driver/v2/bson"
)
func TestPayloadForItem_Object(t *testing.T) {
got, err := payloadForItem("coin", "user:1", []byte(`{"bal":100,"meta":{"createdAt":5}}`))
if err != nil {
t.Fatalf("payloadForItem: %v", err)
}
if got["bal"] != int64(100) {
t.Errorf("bal = %v (%T), want int64(100)", got["bal"], got["bal"])
}
if _, ok := got["value"]; ok {
t.Error("payload must not contain a 'value' envelope field")
}
meta, ok := got["meta"].(bson.M)
if !ok || meta["createdAt"] != int64(5) {
t.Errorf("nested meta = %v, want {createdAt: int64(5)}", got["meta"])
}
}
func TestPayloadForItem_LolscheduleSubscribers(t *testing.T) {
got, err := payloadForItem("lolschedule", "subscribers", []byte(`[{"chatId":1},{"chatId":2}]`))
if err != nil {
t.Fatalf("payloadForItem: %v", err)
}
arr, ok := got["subscribers"].(bson.A)
if !ok || len(arr) != 2 {
t.Fatalf("subscribers = %v (%T), want 2-element bson.A", got["subscribers"], got["subscribers"])
}
}
func TestPayloadForItem_LolscheduleLastPushRawString(t *testing.T) {
// last-push date is stored as a bare (non-JSON) string in DynamoDB.
got, err := payloadForItem("lolschedule", "daily_push:last_date", []byte(`2026-06-28`))
if err != nil {
t.Fatalf("payloadForItem: %v", err)
}
if got["date"] != "2026-06-28" {
t.Errorf("date = %v, want 2026-06-28", got["date"])
}
}
func TestPayloadForItem_UnknownNonObjectFailsLoud(t *testing.T) {
if _, err := payloadForItem("coin", "user:1", []byte(`"a-bare-string"`)); err == nil {
t.Error("non-object value with no wrap rule must fail loud")
}
if _, err := payloadForItem("coin", "user:1", []byte(`[1,2,3]`)); err == nil {
t.Error("array value with no wrap rule must fail loud")
}
}
func TestPayloadForItem_ReservedKeyCollision(t *testing.T) {
if _, err := payloadForItem("coin", "user:1", []byte(`{"version":7}`)); err == nil {
t.Error("payload key colliding with reserved root field must fail loud")
}
}
// TestPayloadForItem_CamelCaseFidelity guards the invariant that lets migration
// work: the migrator preserves the original (camelCase) JSON keys, so a typed
// store struct must declare bson tags matching those names. A struct whose bson
// tags drifted to the driver's lowercased default would read these back empty.
func TestPayloadForItem_CamelCaseFidelity(t *testing.T) {
payload, err := payloadForItem("lolschedule", "events", []byte(`{"startTime":"t","gameWins":3,"blockName":"Week 1"}`))
if err != nil {
t.Fatalf("payloadForItem: %v", err)
}
raw, err := bson.Marshal(payload)
if err != nil {
t.Fatalf("bson.Marshal: %v", err)
}
var got struct {
StartTime string `bson:"startTime"`
GameWins int `bson:"gameWins"`
BlockName string `bson:"blockName"`
}
if err := bson.Unmarshal(raw, &got); err != nil {
t.Fatalf("bson.Unmarshal: %v", err)
}
if got.StartTime != "t" || got.GameWins != 3 || got.BlockName != "Week 1" {
t.Fatalf("camelCase round-trip lost data: %+v", got)
}
}