2026-05-13 16:07:25 +08:00

130 lines
4.5 KiB
Go

package walletnotice
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"time"
_ "github.com/go-sql-driver/mysql"
)
func TestMySQLRepositoryProcessesRealWalletOutbox(t *testing.T) {
dsn := strings.TrimSpace(os.Getenv("NOTICE_REAL_MYSQL_DSN"))
if dsn == "" {
t.Skip("set NOTICE_REAL_MYSQL_DSN to run real MySQL notice validation")
}
walletDatabase := strings.TrimSpace(os.Getenv("NOTICE_REAL_WALLET_DATABASE"))
if walletDatabase == "" {
walletDatabase = "hyapp_wallet"
}
db, err := sql.Open("mysql", dsn)
if err != nil {
t.Fatalf("open mysql: %v", err)
}
defer db.Close()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
t.Fatalf("ping mysql: %v", err)
}
repository, err := NewMySQLRepository(db, walletDatabase)
if err != nil {
t.Fatalf("NewMySQLRepository: %v", err)
}
nowMs := time.Now().UnixMilli()
suffix := fmt.Sprintf("%d", time.Now().UnixNano())
appCode := "notice_real"
userID := int64(990000000)
eventID := "notice_real_evt_" + suffix
transactionID := "notice_real_tx_" + suffix
commandID := "notice_real_cmd_" + suffix
payload := map[string]any{
"transaction_id": transactionID,
"command_id": commandID,
"user_id": userID,
"asset_type": "COIN",
"available_delta": int64(188),
"frozen_delta": int64(0),
"available_after": int64(99887766),
"frozen_after": int64(0),
"balance_version": int64(66),
"metadata": map[string]any{
"validation": "notice-service-real-mysql",
},
"created_at_ms": nowMs,
}
payloadJSON, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal payload: %v", err)
}
if _, err := db.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO %s.wallet_outbox (
app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type,
available_delta, frozen_delta, payload, status, retry_count, last_error, created_at_ms, updated_at_ms
) VALUES (?, ?, 'WalletBalanceChanged', ?, ?, ?, 'COIN', 188, 0, ?, 'pending', 0, '', ?, ?)`,
quoteDB(walletDatabase),
), appCode, eventID, transactionID, commandID, userID, string(payloadJSON), nowMs, nowMs); err != nil {
t.Fatalf("insert wallet_outbox: %v", err)
}
if !keepRealRows() {
t.Cleanup(func() {
_, _ = db.ExecContext(context.Background(), `DELETE FROM notice_delivery_events WHERE app_code = ? AND source_event_id = ?`, appCode, eventID)
_, _ = db.ExecContext(context.Background(), fmt.Sprintf(`DELETE FROM %s.wallet_outbox WHERE app_code = ? AND event_id = ?`, quoteDB(walletDatabase)), appCode, eventID)
})
}
publisher := &fakePublisher{}
service := New(Config{NodeID: "notice-real-test"}, repository, publisher)
processed, err := service.ProcessWalletBalanceNotices(ctx, WalletNoticeWorkerOptions{
WorkerID: "notice-real-test-worker",
BatchSize: 10,
LockTTL: 30 * time.Second,
PublishTimeout: time.Second,
MaxRetryCount: 3,
})
if err != nil {
t.Fatalf("ProcessWalletBalanceNotices: %v", err)
}
if processed != 1 || len(publisher.messages) != 1 {
t.Fatalf("unexpected process result: processed=%d messages=%d", processed, len(publisher.messages))
}
var noticeStatus string
var deliveredAtMS int64
var storedPayload string
if err := db.QueryRowContext(ctx, `
SELECT status, delivered_at_ms, COALESCE(CAST(payload_json AS CHAR), '{}')
FROM notice_delivery_events
WHERE source_name = 'wallet_outbox' AND app_code = ? AND source_event_id = ? AND channel = 'tencent_im_c2c'`,
appCode, eventID,
).Scan(&noticeStatus, &deliveredAtMS, &storedPayload); err != nil {
t.Fatalf("query notice_delivery_events: %v", err)
}
if noticeStatus != "delivered" || deliveredAtMS <= 0 {
t.Fatalf("unexpected delivery marker: status=%s delivered_at_ms=%d payload=%s", noticeStatus, deliveredAtMS, storedPayload)
}
var noticePayload map[string]any
if err := json.Unmarshal([]byte(storedPayload), &noticePayload); err != nil {
t.Fatalf("decode stored notice payload: %v", err)
}
if noticePayload["event_id"] != eventID || noticePayload["asset_type"] != "COIN" || noticePayload["balance_version"].(float64) != 66 {
t.Fatalf("unexpected stored notice payload: %+v", noticePayload)
}
t.Logf("real notice validation event_id=%s status=%s delivered_at_ms=%d", eventID, noticeStatus, deliveredAtMS)
}
func keepRealRows() bool {
value := strings.ToLower(strings.TrimSpace(os.Getenv("NOTICE_REAL_MYSQL_KEEP_ROWS")))
return value == "1" || value == "true" || value == "yes"
}
func quoteDB(value string) string {
return "`" + strings.ReplaceAll(value, "`", "``") + "`"
}