130 lines
4.5 KiB
Go
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(¬iceStatus, &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), ¬icePayload); 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, "`", "``") + "`"
|
|
}
|