package mysql import ( "os" "path/filepath" "runtime" "strings" "testing" "hyapp/pkg/walletmq" ) func TestResourceOutboxEventsUseStableEventIDAsTransactionID(t *testing.T) { // 这些是当前所有通过 resourceOutboxEvent 创建的资源事实;helper 对事件类型 // 本身无分支,因此该约束也自动覆盖以后接入同一入口的新资源事实。 eventTypes := []string{ "GiftConfigChanged", "ResourceChanged", "ResourceGrantRevoked", "ResourceGranted", "ResourceGroupChanged", "ResourceGroupGranted", "UserResourceChanged", "VipEffectiveChanged", "VipTrialCardGranted", } for _, eventType := range eventTypes { t.Run(eventType, func(t *testing.T) { first := resourceOutboxEvent(eventType, "command-1", 1001, 2001, map[string]any{"source": "test"}, 1_700_000_000_000) second := resourceOutboxEvent(eventType, "command-1", 1001, 2001, map[string]any{"source": "retry"}, 1_700_000_000_999) if first.TransactionID == "" || first.TransactionID != first.EventID { t.Fatalf("resource correlation ids mismatch: event_id=%q transaction_id=%q", first.EventID, first.TransactionID) } if second.EventID != first.EventID || second.TransactionID != first.TransactionID { t.Fatalf("resource correlation id changed on retry: first=%+v second=%+v", first, second) } if len(first.TransactionID) > 96 { t.Fatalf("resource transaction_id exceeds wallet_outbox column: %d", len(first.TransactionID)) } if _, err := walletmq.EncodeWalletOutboxMessage(walletmq.WalletOutboxMessage{ AppCode: "lalu", EventID: first.EventID, EventType: first.EventType, TransactionID: first.TransactionID, CommandID: first.CommandID, UserID: first.UserID, AssetType: first.AssetType, PayloadJSON: `{"source":"test"}`, OccurredAtMS: first.CreatedAtMS, }); err != nil { t.Fatalf("resource event is still rejected by wallet MQ encoder: %v", err) } }) } } func TestResourceOutboxHistoricalRetryIsOptInAndNarrowlyScoped(t *testing.T) { _, file, _, ok := runtime.Caller(0) if !ok { t.Fatal("resolve test source path") } path := filepath.Join(filepath.Dir(file), "../../../deploy/mysql/maintenance/20260711_retry_incomplete_resource_outbox.sql") body, err := os.ReadFile(path) if err != nil { t.Fatalf("read resource outbox maintenance SQL: %v", err) } sql := string(body) for _, required := range []string{ "COALESCE(@execute_wallet_resource_outbox_retry, 0)", "WHERE @execute_wallet_resource_outbox_retry = 1", "AND wo.asset_type = 'RESOURCE'", "AND wo.transaction_id = ''", "AND BINARY wo.last_error = BINARY 'wallet outbox message is incomplete'", "AND wo.status IN ('failed', 'retryable')", "LIMIT 100", "SET wo.transaction_id = wo.event_id", "wo.status = 'pending'", } { if !strings.Contains(sql, required) { t.Fatalf("maintenance SQL misses safety contract %q", required) } } for _, eventType := range []string{ "GiftConfigChanged", "ResourceChanged", "ResourceGrantRevoked", "ResourceGranted", "ResourceGroupChanged", "ResourceGroupGranted", "UserResourceChanged", "VipEffectiveChanged", "VipTrialCardGranted", } { if !strings.Contains(sql, "'"+eventType+"'") { t.Fatalf("maintenance SQL misses resource event type %q", eventType) } } }