// Package mysqltest provides real wallet-service MySQL repositories for tests. package mysqltest import ( "context" "fmt" "testing" "time" "hyapp/internal/testutil/mysqlschema" mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql" ) // Repository wraps the production MySQL wallet repository with test seed helpers. type Repository struct { *mysqlstorage.Repository t testing.TB schema *mysqlschema.Schema } // CountRows returns a simple count from a known wallet table for persistence assertions. func (r *Repository) CountRows(table string, where string, args ...any) int { r.t.Helper() query := "SELECT COUNT(*) FROM " + validateTableName(table) if where != "" { query += " WHERE " + where } var count int if err := r.schema.DB.QueryRowContext(context.Background(), query, args...).Scan(&count); err != nil { r.t.Fatalf("count %s rows failed: %v\nquery=%s", table, err, query) } return count } // InsertRawGiftPrice allows tests to seed inactive or future prices without service helpers. func (r *Repository) InsertRawGiftPrice(giftID string, priceVersion string, status string, effectiveAtMs int64) { r.t.Helper() nowMs := time.Now().UnixMilli() _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO wallet_gift_prices ( gift_id, price_version, status, coin_price, gift_point_amount, heat_value, effective_at_ms, created_at_ms, updated_at_ms ) VALUES (?, ?, ?, 1, 1, 1, ?, ?, ?) `, giftID, priceVersion, status, effectiveAtMs, nowMs, nowMs) if err != nil { r.t.Fatalf("insert raw gift price failed: %v", err) } } // NewRepository creates an isolated wallet schema and opens the production repository. func NewRepository(t testing.TB) *Repository { t.Helper() schema := mysqlschema.New(t, mysqlschema.Config{ EnvVar: "WALLET_SERVICE_MYSQL_TEST_DSN", InitDBPath: initDBPath(t), DatabasePrefix: "hyapp_wallet_test", }) repository, err := mysqlstorage.Open(context.Background(), schema.DSN) if err != nil { t.Fatalf("open wallet mysql repository failed: %v", err) } wrapped := &Repository{Repository: repository, t: t, schema: schema} t.Cleanup(wrapped.Close) return wrapped } // Close shuts down the repository; mysqlschema owns schema cleanup. func (r *Repository) Close() { r.t.Helper() if r.Repository != nil { _ = r.Repository.Close() } } // SetBalance prepares a sender COIN account for debit tests. func (r *Repository) SetBalance(userID int64, balance int64) { r.SetAssetBalance(userID, "COIN", balance) } // SetAssetBalance prepares a sender account for debit tests. func (r *Repository) SetAssetBalance(userID int64, assetType string, balance int64) { r.t.Helper() nowMs := time.Now().UnixMilli() _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO wallet_accounts (user_id, asset_type, available_amount, frozen_amount, version, created_at_ms, updated_at_ms) VALUES (?, ?, ?, 0, 1, ?, ?) ON DUPLICATE KEY UPDATE available_amount = VALUES(available_amount), frozen_amount = VALUES(frozen_amount), version = version + 1, updated_at_ms = VALUES(updated_at_ms) `, userID, assetType, balance, nowMs, nowMs) if err != nil { r.t.Fatalf("seed wallet balance failed: %v", err) } } // SetGiftPrice overrides or inserts a gift settlement price for server-side billing tests. func (r *Repository) SetGiftPrice(giftID string, priceVersion string, coinPrice int64, giftPointAmount int64, heatValue int64) { r.t.Helper() nowMs := time.Now().UnixMilli() _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO wallet_gift_prices ( gift_id, price_version, status, coin_price, gift_point_amount, heat_value, effective_at_ms, created_at_ms, updated_at_ms ) VALUES (?, ?, 'active', ?, ?, ?, 0, ?, ?) ON DUPLICATE KEY UPDATE status = VALUES(status), coin_price = VALUES(coin_price), gift_point_amount = VALUES(gift_point_amount), heat_value = VALUES(heat_value), updated_at_ms = VALUES(updated_at_ms) `, giftID, priceVersion, coinPrice, giftPointAmount, heatValue, nowMs, nowMs) if err != nil { r.t.Fatalf("seed wallet gift price failed: %v", err) } } // SetRechargePolicy 配置区域充值汇率,用于币商转账充值口径测试。 func (r *Repository) SetRechargePolicy(regionID int64, policyVersion string, coinAmount int64, usdMinorAmount int64) { r.t.Helper() nowMs := time.Now().UnixMilli() _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO wallet_recharge_policies ( region_id, policy_version, status, currency_code, coin_amount, usd_minor_amount, effective_from_ms, created_at_ms, updated_at_ms ) VALUES (?, ?, 'active', 'USD', ?, ?, 0, ?, ?) ON DUPLICATE KEY UPDATE status = VALUES(status), currency_code = VALUES(currency_code), coin_amount = VALUES(coin_amount), usd_minor_amount = VALUES(usd_minor_amount), updated_at_ms = VALUES(updated_at_ms) `, regionID, policyVersion, coinAmount, usdMinorAmount, nowMs, nowMs) if err != nil { r.t.Fatalf("seed wallet recharge policy failed: %v", err) } } // SetRechargeStats prepares cumulative recharge stats for VIP gate tests. func (r *Repository) SetRechargeStats(userID int64, totalCoinAmount int64) { r.t.Helper() nowMs := time.Now().UnixMilli() _, err := r.schema.DB.ExecContext(context.Background(), ` INSERT INTO wallet_user_recharge_stats ( user_id, recharge_count, first_transaction_id, first_recharged_at_ms, total_coin_amount, total_usd_minor_amount, created_at_ms, updated_at_ms ) VALUES (?, 1, 'test_recharge', ?, ?, 0, ?, ?) ON DUPLICATE KEY UPDATE total_coin_amount = VALUES(total_coin_amount), updated_at_ms = VALUES(updated_at_ms) `, userID, nowMs, totalCoinAmount, nowMs, nowMs) if err != nil { r.t.Fatalf("seed wallet recharge stats failed: %v", err) } } func initDBPath(t testing.TB) string { t.Helper() return mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_wallet_service.sql", ) } func validateTableName(table string) string { switch table { case "wallet_transactions", "wallet_entries", "wallet_outbox", "wallet_accounts", "wallet_recharge_records", "coin_seller_stock_records", "wallet_diamond_exchange_rules", "wallet_withdrawal_requests", "vip_levels", "user_vip_memberships", "vip_purchase_orders", "user_vip_history", "resources", "resource_groups", "resource_group_items", "resource_shop_items", "resource_shop_purchase_orders", "gift_type_configs", "gift_configs", "gift_config_regions", "user_gift_wall", "wallet_projection_events", "resource_grants", "resource_grant_items", "user_resource_entitlements", "user_resource_equipment": return table default: panic(fmt.Sprintf("unsupported wallet test table %q", table)) } }