127 lines
4.7 KiB
Go
127 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|