package dashboard import ( "context" "database/sql" "fmt" "os" "strings" "testing" "time" mysqlDriver "github.com/go-sql-driver/mysql" ) func TestUserProfileVersionDistributionRealMySQL(t *testing.T) { baseDSN := strings.TrimSpace(os.Getenv("DASHBOARD_USER_PROFILE_MYSQL_TEST_DSN")) if baseDSN == "" { t.Skip("set DASHBOARD_USER_PROFILE_MYSQL_TEST_DSN to run the real MySQL user-profile query") } // sqlmock only matches text and previously let a reserved-word alias reach production. This focused // integration test asks a real MySQL parser/optimizer to execute the LATERAL query and its tie-break rule. db := newUserProfileMySQLTestDB(t, baseDSN) execUserProfileSQL(t, db, `CREATE TABLE users ( app_code VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, gender VARCHAR(32) NULL, profile_completed TINYINT(1) NOT NULL DEFAULT 0, source VARCHAR(64) NULL, PRIMARY KEY (app_code, user_id) ) ENGINE=InnoDB`, `CREATE TABLE login_audit ( app_code VARCHAR(32) NOT NULL, id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NULL, login_type VARCHAR(32) NOT NULL, app_version VARCHAR(64) NOT NULL DEFAULT '', result VARCHAR(32) NOT NULL, blocked TINYINT(1) NOT NULL DEFAULT 0, created_at_ms BIGINT NOT NULL, KEY idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id) ) ENGINE=InnoDB`, `INSERT INTO users (app_code, user_id, gender, profile_completed, source) VALUES ('lalu', 1, 'male', 1, 'third_party'), ('lalu', 2, 'female', 1, 'password'), ('lalu', 3, 'male', 1, 'quick_account'), ('lalu', 4, 'female', 1, 'third_party'), ('lalu', 5, 'male', 1, 'password'), ('lalu', 6, 'male', 1, 'game_robot'), ('lalu', 7, 'female', 0, 'third_party'), ('lalu', 9, 'female', 1, 'third_party'), ('huwaa', 1, 'female', 1, 'third_party')`, `INSERT INTO login_audit (app_code, user_id, login_type, app_version, result, blocked, created_at_ms) VALUES ('lalu', 1, 'password', '1.0.0', 'success', 0, 100), ('lalu', 1, 'third_party', '2.0.0', 'success', 0, 200), ('lalu', 1, 'refresh', '3.0.0', 'success', 0, 200), ('lalu', 3, 'refresh', 'excluded', 'success', 0, 500), ('lalu', 4, 'password', '', 'success', 0, 100), ('lalu', 4, 'third_party', 'blocked', 'success', 1, 300), ('lalu', 4, 'refresh', 'failed', 'failed', 0, 400), ('lalu', 4, 'logout', 'unsupported', 'success', 0, 500), ('lalu', 5, 'password', 'time-wins', 'success', 0, 500), ('lalu', 5, 'password', 'larger-id-loses', 'success', 0, 100), ('lalu', 6, 'password', 'excluded', 'success', 0, 500), ('lalu', 7, 'password', 'excluded', 'success', 0, 500), ('lalu', 9, 'password', ' ', 'success', 0, 500), ('huwaa', 1, 'password', 'other-app', 'success', 0, 500)`, ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() items, err := (&DashboardService{userDB: db}).queryNaturalUserAppVersionDistribution(ctx, "lalu") if err != nil { t.Fatalf("query real MySQL version distribution: %v", err) } if len(items) != 3 || items[0] != (UserProfileDistributionItem{Key: "unknown", Label: "未知版本", Count: 3}) || items[1] != (UserProfileDistributionItem{Key: "3.0.0", Label: "3.0.0", Count: 1}) || items[2] != (UserProfileDistributionItem{Key: "time-wins", Label: "time-wins", Count: 1}) { t.Fatalf("version distribution mismatch: %+v", items) } } func newUserProfileMySQLTestDB(t *testing.T, baseDSN string) *sql.DB { t.Helper() baseConfig, err := mysqlDriver.ParseDSN(baseDSN) if err != nil { t.Fatalf("parse MySQL test DSN: %v", err) } databaseName := fmt.Sprintf("hy_admin_user_profile_%d", time.Now().UnixNano()) adminConfig := baseConfig.Clone() adminConfig.DBName = "" adminDB, err := sql.Open("mysql", adminConfig.FormatDSN()) if err != nil { t.Fatalf("open MySQL admin connection: %v", err) } if _, err := adminDB.Exec("CREATE DATABASE `" + databaseName + "` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); err != nil { _ = adminDB.Close() t.Fatalf("create MySQL test database: %v", err) } testConfig := baseConfig.Clone() testConfig.DBName = databaseName db, err := sql.Open("mysql", testConfig.FormatDSN()) if err != nil { _, _ = adminDB.Exec("DROP DATABASE IF EXISTS `" + databaseName + "`") _ = adminDB.Close() t.Fatalf("open MySQL test database: %v", err) } t.Cleanup(func() { _ = db.Close() _, _ = adminDB.Exec("DROP DATABASE IF EXISTS `" + databaseName + "`") _ = adminDB.Close() }) return db } func execUserProfileSQL(t *testing.T, db *sql.DB, statements ...string) { t.Helper() for _, statement := range statements { if _, err := db.Exec(statement); err != nil { t.Fatalf("execute user-profile fixture SQL: %v", err) } } }