package mysql import ( "context" "regexp" "testing" "github.com/DATA-DOG/go-sqlmock" ) func TestMigrateAddsLoginAuditClientVersionColumnsAndIndex(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("create sqlmock: %v", err) } defer db.Close() repository := &Repository{db: db} columnProbe := `(?s)SELECT COUNT\(\*\).*FROM information_schema\.COLUMNS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*COLUMN_NAME = \?` mock.ExpectQuery(columnProbe). WithArgs("login_audit", "app_version"). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD COLUMN app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端版本' AFTER platform")). WillReturnResult(sqlmock.NewResult(0, 0)) mock.ExpectQuery(columnProbe). WithArgs("login_audit", "build_number"). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD COLUMN build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端构建号' AFTER app_version")). WillReturnResult(sqlmock.NewResult(0, 0)) for _, column := range []string{"token_family_id", "generation", "parent_session_id", "rotated_to_session_id", "rotation_at_ms", "rotation_request_id"} { mock.ExpectQuery(columnProbe). WithArgs("auth_sessions", column). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) } requiredIndexProbe := `(?s)SELECT COLUMN_NAME, NON_UNIQUE.*FROM information_schema\.STATISTICS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*INDEX_NAME = \?.*ORDER BY SEQ_IN_INDEX` mock.ExpectQuery(requiredIndexProbe). WithArgs("auth_sessions", "idx_auth_sessions_token_family"). WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME", "NON_UNIQUE"}). AddRow("app_code", 1). AddRow("token_family_id", 1). AddRow("generation", 1)) mock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS auth_refresh_outcomes`). WillReturnResult(sqlmock.NewResult(0, 0)) mock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS auth_session_denylist_jobs`). WillReturnResult(sqlmock.NewResult(0, 0)) mock.ExpectQuery(columnProbe). WithArgs("auth_session_denylist_jobs", "deny_until_ms"). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) mock.ExpectQuery(requiredIndexProbe). WithArgs("auth_session_denylist_jobs", "uk_auth_session_denylist_job"). WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME", "NON_UNIQUE"}). AddRow("app_code", 0). AddRow("session_id", 0)) indexProbe := `(?s)SELECT COLUMN_NAME.*FROM information_schema\.STATISTICS.*TABLE_SCHEMA = DATABASE\(\).*TABLE_NAME = \?.*INDEX_NAME = \?.*ORDER BY SEQ_IN_INDEX` mock.ExpectQuery(indexProbe). WithArgs("auth_session_denylist_jobs", "idx_auth_session_denylist_cleanup"). WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME"}). AddRow("deny_until_ms"). AddRow("job_id")) mock.ExpectQuery(indexProbe). WithArgs("login_audit", "idx_login_audit_latest_success"). WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME"})) mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE login_audit ADD INDEX idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id)")). WillReturnResult(sqlmock.NewResult(0, 0)) if err := repository.Migrate(context.Background()); err != nil { t.Fatalf("migrate login audit client version schema: %v", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("migration expectations mismatch: %v", err) } }