169 lines
6.3 KiB
Go
169 lines
6.3 KiB
Go
package wallet_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
|
|
"hyapp/services/wallet-service/internal/domain/ledger"
|
|
walletservice "hyapp/services/wallet-service/internal/service/wallet"
|
|
mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql"
|
|
)
|
|
|
|
func TestRealMySQLGrantVipWritesActivationOutbox(t *testing.T) {
|
|
dsn := strings.TrimSpace(os.Getenv("WALLET_REAL_MYSQL_DSN"))
|
|
if dsn == "" {
|
|
t.Skip("set WALLET_REAL_MYSQL_DSN to run real MySQL VIP grant validation")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
db, err := sql.Open("mysql", dsn)
|
|
if err != nil {
|
|
t.Fatalf("open mysql: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
if err := db.PingContext(ctx); err != nil {
|
|
t.Fatalf("ping mysql: %v", err)
|
|
}
|
|
|
|
repository, err := mysqlstorage.Open(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatalf("open wallet repository: %v", err)
|
|
}
|
|
defer repository.Close()
|
|
|
|
level, vipName := realActiveVIPLevel(t, ctx, db)
|
|
suffix := time.Now().UnixNano()
|
|
userID := int64(990100000 + suffix%100000)
|
|
commandID := fmt.Sprintf("real_vip_grant_%d", suffix)
|
|
operatorUserID := int64(990000001)
|
|
if !keepWalletRealRows() {
|
|
t.Cleanup(func() {
|
|
cleanupRealVIPGrantRows(context.Background(), t, db, userID, commandID)
|
|
})
|
|
}
|
|
|
|
service := walletservice.New(repository)
|
|
receipt, err := service.GrantVip(ctx, ledger.GrantVipCommand{
|
|
CommandID: commandID,
|
|
AppCode: "lalu",
|
|
TargetUserID: userID,
|
|
Level: level,
|
|
GrantSource: ledger.VipGrantSourceAdmin,
|
|
OperatorUserID: operatorUserID,
|
|
Reason: "real mysql vip grant validation",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GrantVip real mysql failed: %v", err)
|
|
}
|
|
if receipt.TransactionID == "" || receipt.Vip.UserID != userID || receipt.Vip.Level != level || !receipt.Vip.Active {
|
|
t.Fatalf("unexpected GrantVip receipt: %+v", receipt)
|
|
}
|
|
|
|
again, err := service.GrantVip(ctx, ledger.GrantVipCommand{
|
|
CommandID: commandID,
|
|
AppCode: "lalu",
|
|
TargetUserID: userID,
|
|
Level: level,
|
|
GrantSource: ledger.VipGrantSourceAdmin,
|
|
OperatorUserID: operatorUserID,
|
|
Reason: "real mysql vip grant validation",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GrantVip real mysql retry failed: %v", err)
|
|
}
|
|
if again.TransactionID != receipt.TransactionID || again.Vip.Level != receipt.Vip.Level {
|
|
t.Fatalf("GrantVip idempotency mismatch: first=%+v again=%+v", receipt, again)
|
|
}
|
|
|
|
var membershipLevel int32
|
|
var membershipName string
|
|
if err := db.QueryRowContext(ctx, `
|
|
SELECT level, name
|
|
FROM user_vip_memberships
|
|
WHERE app_code = 'lalu' AND user_id = ? AND status = 'active'`,
|
|
userID,
|
|
).Scan(&membershipLevel, &membershipName); err != nil {
|
|
t.Fatalf("query real vip membership: %v", err)
|
|
}
|
|
if membershipLevel != level || membershipName != vipName {
|
|
t.Fatalf("unexpected real vip membership: level=%d name=%s", membershipLevel, membershipName)
|
|
}
|
|
|
|
var outboxStatus string
|
|
var assetType string
|
|
var payloadRaw string
|
|
if err := db.QueryRowContext(ctx, `
|
|
SELECT status, asset_type, COALESCE(CAST(payload AS CHAR), '{}')
|
|
FROM wallet_outbox
|
|
WHERE app_code = 'lalu' AND transaction_id = ? AND event_type = 'VipActivated'`,
|
|
receipt.TransactionID,
|
|
).Scan(&outboxStatus, &assetType, &payloadRaw); err != nil {
|
|
t.Fatalf("query real vip outbox: %v", err)
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal([]byte(payloadRaw), &payload); err != nil {
|
|
t.Fatalf("decode real vip outbox payload: %v", err)
|
|
}
|
|
if outboxStatus != "pending" || assetType != "VIP" || payload["event_type"] != "VipActivated" || payload["source"] != ledger.VipGrantSourceAdmin || int32(payload["level"].(float64)) != level {
|
|
t.Fatalf("unexpected real vip outbox: status=%s asset=%s payload=%+v", outboxStatus, assetType, payload)
|
|
}
|
|
t.Logf("real wallet VIP grant user_id=%d level=%d transaction_id=%s outbox_status=%s", userID, level, receipt.TransactionID, outboxStatus)
|
|
}
|
|
|
|
func realActiveVIPLevel(t *testing.T, ctx context.Context, db *sql.DB) (int32, string) {
|
|
t.Helper()
|
|
|
|
var level int32
|
|
var name string
|
|
err := db.QueryRowContext(ctx, `
|
|
SELECT level, name
|
|
FROM vip_levels
|
|
WHERE app_code = 'lalu' AND status = 'active'
|
|
ORDER BY level ASC
|
|
LIMIT 1`,
|
|
).Scan(&level, &name)
|
|
if err == sql.ErrNoRows {
|
|
t.Skip("real hyapp_wallet.vip_levels has no active VIP level")
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("query active vip level: %v", err)
|
|
}
|
|
return level, name
|
|
}
|
|
|
|
func cleanupRealVIPGrantRows(ctx context.Context, t testing.TB, db *sql.DB, userID int64, commandID string) {
|
|
t.Helper()
|
|
|
|
rewardCommandID := "vip_reward:" + commandID
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM wallet_outbox WHERE app_code = 'lalu' AND (command_id = ? OR command_id = ? OR command_id LIKE ?)`, commandID, rewardCommandID, "resource_group_grant:"+rewardCommandID+"%")
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM wallet_entries WHERE app_code = 'lalu' AND user_id = ? AND transaction_id IN (SELECT transaction_id FROM wallet_transactions WHERE app_code = 'lalu' AND (command_id = ? OR command_id LIKE ?))`, userID, commandID, "resource_group_grant:"+rewardCommandID+"%")
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM wallet_transactions WHERE app_code = 'lalu' AND (command_id = ? OR command_id = ? OR command_id LIKE ?)`, commandID, rewardCommandID, "resource_group_grant:"+rewardCommandID+"%")
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM user_resource_equipment WHERE app_code = 'lalu' AND user_id = ?`, userID)
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM user_resource_entitlements WHERE app_code = 'lalu' AND user_id = ?`, userID)
|
|
_, _ = db.ExecContext(ctx, `
|
|
DELETE rgi
|
|
FROM resource_grant_items rgi
|
|
JOIN resource_grants rg ON rg.app_code = rgi.app_code AND rg.grant_id = rgi.grant_id
|
|
WHERE rg.app_code = 'lalu' AND rg.target_user_id = ? AND rg.command_id = ?`,
|
|
userID, rewardCommandID,
|
|
)
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM resource_grants WHERE app_code = 'lalu' AND target_user_id = ? AND command_id = ?`, userID, rewardCommandID)
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM user_vip_history WHERE app_code = 'lalu' AND user_id = ?`, userID)
|
|
_, _ = db.ExecContext(ctx, `DELETE FROM user_vip_memberships WHERE app_code = 'lalu' AND user_id = ?`, userID)
|
|
}
|
|
|
|
func keepWalletRealRows() bool {
|
|
value := strings.ToLower(strings.TrimSpace(os.Getenv("WALLET_REAL_MYSQL_KEEP_ROWS")))
|
|
return value == "1" || value == "true" || value == "yes"
|
|
}
|