diff --git a/local/migrations/003_update_state_history_sessions.go b/local/migrations/003_update_state_history_sessions.go new file mode 100644 index 0000000..0092c22 --- /dev/null +++ b/local/migrations/003_update_state_history_sessions.go @@ -0,0 +1,124 @@ +package migrations + +import ( + "acc-server-manager/local/utl/logging" + "fmt" + + "gorm.io/gorm" +) + +// UpdateStateHistorySessions migrates tables from integer IDs to UUIDs +type UpdateStateHistorySessions struct { + DB *gorm.DB +} + +// NewUpdateStateHistorySessions creates a new UUID migration +func NewUpdateStateHistorySessions(db *gorm.DB) *UpdateStateHistorySessions { + return &UpdateStateHistorySessions{DB: db} +} + +// Up executes the migration +func (m *UpdateStateHistorySessions) Up() error { + logging.Info("Checking UUID migration...") + + // Check if migration is needed by looking at the servers table structure + if !m.needsMigration() { + logging.Info("UUID migration not needed - tables already use UUID primary keys") + return nil + } + + logging.Info("Starting UUID migration...") + + // Check if migration has already been applied + var migrationRecord MigrationRecord + err := m.DB.Where("migration_name = ?", "002_migrate_to_uuid").First(&migrationRecord).Error + if err == nil { + logging.Info("UUID migration already applied, skipping") + return nil + } + + // Create migration tracking table if it doesn't exist + if err := m.DB.AutoMigrate(&MigrationRecord{}); err != nil { + return fmt.Errorf("failed to create migration tracking table: %v", err) + } + + // Execute the UUID migration using the existing migration function + logging.Info("Executing UUID migration...") + if err := runUUIDMigrationSQL(m.DB); err != nil { + return fmt.Errorf("failed to execute UUID migration: %v", err) + } + + logging.Info("UUID migration completed successfully") + return nil +} + +// needsMigration checks if the UUID migration is needed by examining table structure +func (m *UpdateStateHistorySessions) needsMigration() bool { + // Check if servers table exists and has integer primary key + var result struct { + Exists bool `gorm:"column:exists"` + } + + err := m.DB.Raw(` + SELECT count(*) > 0 as exists FROM state_history + WHERE length(session) > 1 LIMIT 1; + `).Scan(&result).Error + + if err != nil || !result.Exists { + // Table doesn't exist or no primary key found - assume no migration needed + return false + } + return result.Exists +} + +// Down reverses the migration (not implemented for safety) +func (m *UpdateStateHistorySessions) Down() error { + logging.Error("UUID migration rollback is not supported for data safety reasons") + return fmt.Errorf("UUID migration rollback is not supported") +} + +// runUpdateStateHistorySessionsMigration executes the UUID migration using the SQL file +func runUpdateStateHistorySessionsMigration(db *gorm.DB) error { + // Disable foreign key constraints during migration + if err := db.Exec("PRAGMA foreign_keys=OFF").Error; err != nil { + return fmt.Errorf("failed to disable foreign keys: %v", err) + } + + // Start transaction + tx := db.Begin() + if tx.Error != nil { + return fmt.Errorf("failed to start transaction: %v", tx.Error) + } + + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + migrationSQL := "UPDATE state_history SET session = upper(substr(session, 1, 1));" + + // Execute the migration + if err := tx.Exec(string(migrationSQL)).Error; err != nil { + tx.Rollback() + return fmt.Errorf("failed to execute migration: %v", err) + } + + // Commit transaction + if err := tx.Commit().Error; err != nil { + return fmt.Errorf("failed to commit migration: %v", err) + } + + // Re-enable foreign key constraints + if err := db.Exec("PRAGMA foreign_keys=ON").Error; err != nil { + return fmt.Errorf("failed to re-enable foreign keys: %v", err) + } + + return nil +} + +// RunUpdateStateHistorySessionsMigration is a convenience function to run the migration +func RunUpdateStateHistorySessionsMigration(db *gorm.DB) error { + migration := NewUpdateStateHistorySessions(db) + return migration.Up() +} diff --git a/local/model/config.go b/local/model/config.go index cab0589..3833e78 100644 --- a/local/model/config.go +++ b/local/model/config.go @@ -79,11 +79,11 @@ type EventConfig struct { } type Session struct { - HourOfDay IntString `json:"hourOfDay"` - DayOfWeekend IntString `json:"dayOfWeekend"` - TimeMultiplier IntString `json:"timeMultiplier"` - SessionType string `json:"sessionType"` - SessionDurationMinutes IntString `json:"sessionDurationMinutes"` + HourOfDay IntString `json:"hourOfDay"` + DayOfWeekend IntString `json:"dayOfWeekend"` + TimeMultiplier IntString `json:"timeMultiplier"` + SessionType TrackSession `json:"sessionType"` + SessionDurationMinutes IntString `json:"sessionDurationMinutes"` } type AssistRules struct { diff --git a/local/model/server.go b/local/model/server.go index ec6b1f7..5b62176 100644 --- a/local/model/server.go +++ b/local/model/server.go @@ -73,12 +73,12 @@ type State struct { type ServerState struct { sync.RWMutex `swaggerignore:"-" json:"-"` - Session string `json:"session"` - SessionStart time.Time `json:"sessionStart"` - PlayerCount int `json:"playerCount"` - Track string `json:"track"` - MaxConnections int `json:"maxConnections"` - SessionDurationMinutes int `json:"sessionDurationMinutes"` + Session TrackSession `json:"session"` + SessionStart time.Time `json:"sessionStart"` + PlayerCount int `json:"playerCount"` + Track string `json:"track"` + MaxConnections int `json:"maxConnections"` + SessionDurationMinutes int `json:"sessionDurationMinutes"` // Players map[int]*PlayerState // etc. } diff --git a/local/model/state_history.go b/local/model/state_history.go index 0de270c..1579e32 100644 --- a/local/model/state_history.go +++ b/local/model/state_history.go @@ -1,6 +1,9 @@ package model import ( + "encoding/json" + "fmt" + "strings" "time" "github.com/google/uuid" @@ -13,9 +16,9 @@ type StateHistoryFilter struct { DateRangeFilter // Adds date range filtering // Additional fields specific to state history - Session string `query:"session"` - MinPlayers *int `query:"min_players"` - MaxPlayers *int `query:"max_players"` + Session TrackSession `query:"session"` + MinPlayers *int `query:"min_players"` + MaxPlayers *int `query:"max_players"` } // ApplyFilter implements the Filterable interface @@ -52,16 +55,66 @@ func (f *StateHistoryFilter) ApplyFilter(query *gorm.DB) *gorm.DB { return query } +type TrackSession string + +const ( + SessionPractice TrackSession = "P" + SessionQualify TrackSession = "Q" + SessionRace TrackSession = "R" + SessionUnknown TrackSession = "U" +) + +func (i *TrackSession) UnmarshalJSON(b []byte) error { + var str string + if err := json.Unmarshal(b, &str); err == nil { + *i = ToTrackSession(str) + return nil + } + + return fmt.Errorf("invalid TrackSession value") +} + +func (i TrackSession) Humanize() string { + switch i { + case SessionPractice: + return "Practice" + case SessionQualify: + return "Qualifying" + case SessionRace: + return "Race" + default: + return "Unknown" + } +} + +func ToTrackSession(i string) TrackSession { + sessionAbrv := strings.ToUpper(i[:1]) + switch sessionAbrv { + case "P": + return SessionPractice + case "Q": + return SessionQualify + case "R": + return SessionRace + default: + return SessionUnknown + } +} + +func (i TrackSession) ToString() string { + return string(i) +} + type StateHistory struct { - ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"` - ServerID uuid.UUID `json:"serverId" gorm:"not null;type:uuid"` - Session string `json:"session"` - Track string `json:"track"` - PlayerCount int `json:"playerCount"` - DateCreated time.Time `json:"dateCreated"` - SessionStart time.Time `json:"sessionStart"` - SessionDurationMinutes int `json:"sessionDurationMinutes"` - SessionID uuid.UUID `json:"sessionId" gorm:"not null;type:uuid"` // Unique identifier for each session/event + ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"` + ServerID uuid.UUID `json:"serverId" gorm:"not null;type:uuid"` + Session TrackSession `json:"session"` + Track string `json:"track"` + PlayerCount int `json:"playerCount"` + DateCreated time.Time `json:"dateCreated"` + SessionStart time.Time `json:"sessionStart"` + SessionDurationMinutes int `json:"sessionDurationMinutes"` + SessionID uuid.UUID `json:"sessionId" gorm:"not null;type:uuid"` // Unique identifier for each session/event } // BeforeCreate is a GORM hook that runs before creating new state history entries diff --git a/local/model/state_history_stats.go b/local/model/state_history_stats.go index a501e5d..0a8e853 100644 --- a/local/model/state_history_stats.go +++ b/local/model/state_history_stats.go @@ -3,8 +3,8 @@ package model import "github.com/google/uuid" type SessionCount struct { - Name string `json:"name"` - Count int `json:"count"` + Name TrackSession `json:"name"` + Count int `json:"count"` } type DailyActivity struct { @@ -29,10 +29,10 @@ type StateHistoryStats struct { } type RecentSession struct { - ID uuid.UUID `json:"id"` - Date string `json:"date"` - Type string `json:"type"` - Track string `json:"track"` - Duration int `json:"duration"` - Players int `json:"players"` + ID uuid.UUID `json:"id"` + Date string `json:"date"` + Type TrackSession `json:"type"` + Track string `json:"track"` + Duration int `json:"duration"` + Players int `json:"players"` } diff --git a/local/service/server.go b/local/service/server.go index b0619e7..5c4afec 100644 --- a/local/service/server.go +++ b/local/service/server.go @@ -159,7 +159,7 @@ func (s *ServerService) insertStateHistory(serverID uuid.UUID, state *model.Serv }) } -func (s *ServerService) updateSessionDuration(server *model.Server, sessionType string) { +func (s *ServerService) updateSessionDuration(server *model.Server, sessionType model.TrackSession) { // Get configs using helper methods event, err := s.configService.GetEventConfig(server) if err != nil { diff --git a/local/utl/tracking/tracking.go b/local/utl/tracking/tracking.go index 7f66331..1605ae1 100644 --- a/local/utl/tracking/tracking.go +++ b/local/utl/tracking/tracking.go @@ -11,102 +11,104 @@ import ( ) type StateChange int + const ( - PlayerCount StateChange = iota - Session + PlayerCount StateChange = iota + Session ) -var StateChanges = map[StateChange]string { - PlayerCount: "player-count", - Session: "session", +var StateChanges = map[StateChange]string{ + PlayerCount: "player-count", + Session: "session", } type AccServerInstance struct { - Model *model.Server - State *model.ServerState - OnStateChange func(*model.ServerState, ...StateChange) + Model *model.Server + State *model.ServerState + OnStateChange func(*model.ServerState, ...StateChange) } func NewAccServerInstance(server *model.Server, onStateChange func(*model.ServerState, ...StateChange)) *AccServerInstance { - return &AccServerInstance{ - Model: server, - State: &model.ServerState{PlayerCount: 0}, - OnStateChange: onStateChange, - } + return &AccServerInstance{ + Model: server, + State: &model.ServerState{PlayerCount: 0}, + OnStateChange: onStateChange, + } } type StateRegexHandler struct { - *regex_handler.RegexHandler - test string + *regex_handler.RegexHandler + test string } func NewRegexHandler(str string, test string) *StateRegexHandler { return &StateRegexHandler{ - RegexHandler: regex_handler.New(str), - test: test, + RegexHandler: regex_handler.New(str), + test: test, } } -func (rh *StateRegexHandler) Test(line string) bool{ - return strings.Contains(line, rh.test) +func (rh *StateRegexHandler) Test(line string) bool { + return strings.Contains(line, rh.test) } -func (rh *StateRegexHandler) Count(line string) int{ - var count int = 0 - rh.Contains(line, func (strs ...string) { - if len(strs) == 2 { - if ct, err := strconv.Atoi(strs[1]); err == nil { - count = ct - } - } - }) - return count +func (rh *StateRegexHandler) Count(line string) int { + var count int = 0 + rh.Contains(line, func(strs ...string) { + if len(strs) == 2 { + if ct, err := strconv.Atoi(strs[1]); err == nil { + count = ct + } + } + }) + return count } -func (rh *StateRegexHandler) Change(line string) (string, string){ - var old string = "" - var new string = "" - rh.Contains(line, func (strs ...string) { - if len(strs) == 3 { - old = strs[1] - new = strs[2] - } - }) - return old, new +func (rh *StateRegexHandler) Change(line string) (string, string) { + var old string = "" + var new string = "" + rh.Contains(line, func(strs ...string) { + if len(strs) == 3 { + old = strs[1] + new = strs[2] + } + }) + return old, new } func TailLogFile(path string, callback func(string)) { - file, _ := os.Open(path) - defer file.Close() + file, _ := os.Open(path) + defer file.Close() - file.Seek(0, os.SEEK_END) // Start at end of file - reader := bufio.NewReader(file) + file.Seek(0, os.SEEK_END) // Start at end of file + reader := bufio.NewReader(file) - for { - line, err := reader.ReadString('\n') - if err == nil { - callback(line) - } else { - time.Sleep(500 * time.Millisecond) // wait for new data - } - } + for { + line, err := reader.ReadString('\n') + if err == nil { + callback(line) + } else { + time.Sleep(500 * time.Millisecond) // wait for new data + } + } } type LogStateType int + const ( - SessionChange LogStateType = iota - LeaderboardUpdate - UDPCount - ClientsOnline - RemovingDeadConnection + SessionChange LogStateType = iota + LeaderboardUpdate + UDPCount + ClientsOnline + RemovingDeadConnection ) -var logStateContain = map[LogStateType]string { - SessionChange: "Session changed", - LeaderboardUpdate: "Updated leaderboard for", - UDPCount: "Udp message count", - ClientsOnline: "client(s) online", - RemovingDeadConnection: "Removing dead connection", +var logStateContain = map[LogStateType]string{ + SessionChange: "Session changed", + LeaderboardUpdate: "Updated leaderboard for", + UDPCount: "Udp message count", + ClientsOnline: "client(s) online", + RemovingDeadConnection: "Removing dead connection", } var sessionChangeRegex = NewRegexHandler(`Session changed: (\w+) -> (\w+)`, logStateContain[SessionChange]) @@ -115,75 +117,77 @@ var udpCountRegex = NewRegexHandler(`Udp message count (\d+) client`, logStateCo var clientsOnlineRegex = NewRegexHandler(`(\d+) client\(s\) online`, logStateContain[ClientsOnline]) var removingDeadConnectionsRegex = NewRegexHandler(`Removing dead connection`, logStateContain[RemovingDeadConnection]) -var logStateRegex = map[LogStateType]*StateRegexHandler { - SessionChange: sessionChangeRegex, - LeaderboardUpdate: leaderboardUpdateRegex, - UDPCount: udpCountRegex, - ClientsOnline: clientsOnlineRegex, - RemovingDeadConnection: removingDeadConnectionsRegex, +var logStateRegex = map[LogStateType]*StateRegexHandler{ + SessionChange: sessionChangeRegex, + LeaderboardUpdate: leaderboardUpdateRegex, + UDPCount: udpCountRegex, + ClientsOnline: clientsOnlineRegex, + RemovingDeadConnection: removingDeadConnectionsRegex, } func (instance *AccServerInstance) HandleLogLine(line string) { - for logState, regexHandler := range logStateRegex { - if (regexHandler.Test(line)) { - switch logState { - case LeaderboardUpdate: - case UDPCount: - case ClientsOnline: - count := regexHandler.Count(line) - instance.UpdatePlayerCount(count) - case SessionChange: - _, new := regexHandler.Change(line) - instance.UpdateSessionChange(new) - case RemovingDeadConnection: - instance.UpdatePlayerCount(instance.State.PlayerCount - 1) - } - } - } + for logState, regexHandler := range logStateRegex { + if regexHandler.Test(line) { + switch logState { + case LeaderboardUpdate: + case UDPCount: + case ClientsOnline: + count := regexHandler.Count(line) + instance.UpdatePlayerCount(count) + case SessionChange: + _, new := regexHandler.Change(line) + + trackSession := model.ToTrackSession(new) + instance.UpdateSessionChange(trackSession) + case RemovingDeadConnection: + instance.UpdatePlayerCount(instance.State.PlayerCount - 1) + } + } + } } func (instance *AccServerInstance) UpdateState(callback func(state *model.ServerState, changes *[]StateChange)) { - state := instance.State - changes := []StateChange{} - state.Lock() - defer state.Unlock() - callback(state, &changes) - if (len(changes) > 0) { - instance.OnStateChange(state, changes...) - } + state := instance.State + changes := []StateChange{} + state.Lock() + defer state.Unlock() + callback(state, &changes) + if len(changes) > 0 { + instance.OnStateChange(state, changes...) + } } func (instance *AccServerInstance) UpdatePlayerCount(count int) { - if (count < 0) { - return - } - instance.UpdateState(func (state *model.ServerState, changes *[]StateChange) { - if (count == state.PlayerCount) { - return - } - if (count > 0 && state.PlayerCount == 0) { - state.SessionStart = time.Now() - *changes = append(*changes, Session) - } else if (count == 0) { - state.SessionStart = time.Time{} - *changes = append(*changes, Session) - } - state.PlayerCount = count - *changes = append(*changes, PlayerCount) - }) + if count < 0 { + return + } + instance.UpdateState(func(state *model.ServerState, changes *[]StateChange) { + if count == state.PlayerCount { + return + } + if count > 0 && state.PlayerCount == 0 { + state.SessionStart = time.Now() + *changes = append(*changes, Session) + } else if count == 0 { + state.SessionStart = time.Time{} + *changes = append(*changes, Session) + } + state.PlayerCount = count + *changes = append(*changes, PlayerCount) + }) } -func (instance *AccServerInstance) UpdateSessionChange(session string) { - instance.UpdateState(func (state *model.ServerState, changes *[]StateChange) { - if (session == state.Session) { - return - } - if (state.PlayerCount > 0) { - state.SessionStart = time.Now() - } else { - state.SessionStart = time.Time{} - } - state.Session = session - *changes = append(*changes, Session) - }) -} \ No newline at end of file +func (instance *AccServerInstance) UpdateSessionChange(session model.TrackSession) { + instance.UpdateState(func(state *model.ServerState, changes *[]StateChange) { + if session == state.Session { + return + } + if state.PlayerCount > 0 { + state.SessionStart = time.Now() + } else { + state.SessionStart = time.Time{} + } + state.Session = session + *changes = append(*changes, Session) + }) +} diff --git a/tests/mocks/state_history_mock.go b/tests/mocks/state_history_mock.go index 98af25f..09e1740 100644 --- a/tests/mocks/state_history_mock.go +++ b/tests/mocks/state_history_mock.go @@ -260,7 +260,7 @@ func (m *MockStateHistoryRepository) GetSessionTypes(ctx context.Context, filter } // Group by session type - sessionMap := make(map[string]map[string]bool) // session -> sessionID -> bool + sessionMap := make(map[model.TrackSession]map[string]bool) // session -> sessionID -> bool for _, entry := range filteredEntries { if sessionMap[entry.Session] == nil { sessionMap[entry.Session] = make(map[string]bool) diff --git a/tests/testdata/state_history_data.go b/tests/testdata/state_history_data.go index 5ed6f84..7b6962c 100644 --- a/tests/testdata/state_history_data.go +++ b/tests/testdata/state_history_data.go @@ -22,7 +22,7 @@ func NewStateHistoryTestData(serverID uuid.UUID) *StateHistoryTestData { } // CreateStateHistory creates a basic state history entry -func (td *StateHistoryTestData) CreateStateHistory(session string, track string, playerCount int, sessionID uuid.UUID) model.StateHistory { +func (td *StateHistoryTestData) CreateStateHistory(session model.TrackSession, track string, playerCount int, sessionID uuid.UUID) model.StateHistory { return model.StateHistory{ ID: uuid.New(), ServerID: td.ServerID, @@ -37,7 +37,7 @@ func (td *StateHistoryTestData) CreateStateHistory(session string, track string, } // CreateMultipleEntries creates multiple state history entries for the same session -func (td *StateHistoryTestData) CreateMultipleEntries(session string, track string, playerCounts []int) []model.StateHistory { +func (td *StateHistoryTestData) CreateMultipleEntries(session model.TrackSession, track string, playerCounts []int) []model.StateHistory { sessionID := uuid.New() var entries []model.StateHistory @@ -69,7 +69,7 @@ func CreateBasicFilter(serverID string) *model.StateHistoryFilter { } // CreateFilterWithSession creates a filter with session type -func CreateFilterWithSession(serverID string, session string) *model.StateHistoryFilter { +func CreateFilterWithSession(serverID string, session model.TrackSession) *model.StateHistoryFilter { return &model.StateHistoryFilter{ ServerBasedFilter: model.ServerBasedFilter{ ServerID: serverID, @@ -97,13 +97,13 @@ var SampleLogLines = []string{ // ExpectedSessionChanges represents the expected session changes from parsing the sample log lines var ExpectedSessionChanges = []struct { - From string - To string + From model.TrackSession + To model.TrackSession }{ - {"NONE", "PRACTICE"}, - {"PRACTICE", "QUALIFY"}, - {"QUALIFY", "RACE"}, - {"RACE", "NONE"}, + {model.SessionUnknown, model.SessionPractice}, + {model.SessionPractice, model.SessionQualify}, + {model.SessionQualify, model.SessionRace}, + {model.SessionRace, model.SessionUnknown}, } // ExpectedPlayerCounts represents the expected player counts from parsing the sample log lines diff --git a/tests/unit/controller/state_history_controller_test.go b/tests/unit/controller/state_history_controller_test.go index 4447cdd..a7e0d0d 100644 --- a/tests/unit/controller/state_history_controller_test.go +++ b/tests/unit/controller/state_history_controller_test.go @@ -46,7 +46,7 @@ func TestStateHistoryController_GetAll_Success(t *testing.T) { // Insert test data testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(helper.CreateContext(), &history) tests.AssertNoError(t, err) @@ -78,7 +78,7 @@ func TestStateHistoryController_GetAll_Success(t *testing.T) { err = json.Unmarshal(body, &result) tests.AssertNoError(t, err) tests.AssertEqual(t, 1, len(result)) - tests.AssertEqual(t, "Practice", result[0].Session) + tests.AssertEqual(t, model.SessionPractice, result[0].Session) tests.AssertEqual(t, 5, result[0].PlayerCount) } @@ -107,8 +107,8 @@ func TestStateHistoryController_GetAll_WithSessionFilter(t *testing.T) { // Insert test data with different sessions testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - practiceHistory := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) - raceHistory := testData.CreateStateHistory("Race", "spa", 10, uuid.New()) + practiceHistory := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) + raceHistory := testData.CreateStateHistory(model.SessionRace, "spa", 10, uuid.New()) err := repo.Insert(helper.CreateContext(), &practiceHistory) tests.AssertNoError(t, err) @@ -124,7 +124,7 @@ func TestStateHistoryController_GetAll_WithSessionFilter(t *testing.T) { controller.NewStateHistoryController(stateHistoryService, routeGroups, GetTestAuthMiddleware(membershipService, inMemCache)) // Create request with session filter and authentication - req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/state-history?id=%s&session=Race", helper.TestData.ServerID.String()), nil) + req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/state-history?id=%s&session=R", helper.TestData.ServerID.String()), nil) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+tests.MustGenerateTestToken()) @@ -143,7 +143,7 @@ func TestStateHistoryController_GetAll_WithSessionFilter(t *testing.T) { err = json.Unmarshal(body, &result) tests.AssertNoError(t, err) tests.AssertEqual(t, 1, len(result)) - tests.AssertEqual(t, "Race", result[0].Session) + tests.AssertEqual(t, model.SessionRace, result[0].Session) tests.AssertEqual(t, 10, result[0].PlayerCount) } @@ -220,7 +220,7 @@ func TestStateHistoryController_GetStatistics_Success(t *testing.T) { // Create entries with varying player counts playerCounts := []int{5, 10, 15, 20, 25} - entries := testData.CreateMultipleEntries("Race", "spa", playerCounts) + entries := testData.CreateMultipleEntries(model.SessionRace, "spa", playerCounts) for _, entry := range entries { err := repo.Insert(helper.CreateContext(), &entry) @@ -475,7 +475,7 @@ func TestStateHistoryController_ContentType(t *testing.T) { // Insert test data testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(helper.CreateContext(), &history) tests.AssertNoError(t, err) @@ -551,7 +551,7 @@ func TestStateHistoryController_ResponseStructure(t *testing.T) { // Insert test data testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(helper.CreateContext(), &history) tests.AssertNoError(t, err) diff --git a/tests/unit/repository/state_history_repository_test.go b/tests/unit/repository/state_history_repository_test.go index 5f74c45..d4f1ae5 100644 --- a/tests/unit/repository/state_history_repository_test.go +++ b/tests/unit/repository/state_history_repository_test.go @@ -30,7 +30,7 @@ func TestStateHistoryRepository_Insert_Success(t *testing.T) { // Create test data testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) // Test Insert err := repo.Insert(ctx, &history) @@ -65,7 +65,7 @@ func TestStateHistoryRepository_GetAll_Success(t *testing.T) { // Insert multiple entries playerCounts := []int{0, 5, 10, 15, 10, 5, 0} - entries := testData.CreateMultipleEntries("Practice", "spa", playerCounts) + entries := testData.CreateMultipleEntries(model.SessionPractice, "spa", playerCounts) for _, entry := range entries { err := repo.Insert(ctx, &entry) @@ -101,8 +101,8 @@ func TestStateHistoryRepository_GetAll_WithFilter(t *testing.T) { // Create test data with different sessions testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - practiceHistory := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) - raceHistory := testData.CreateStateHistory("Race", "spa", 15, uuid.New()) + practiceHistory := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) + raceHistory := testData.CreateStateHistory(model.SessionRace, "spa", 15, uuid.New()) // Insert both err := repo.Insert(ctx, &practiceHistory) @@ -111,13 +111,13 @@ func TestStateHistoryRepository_GetAll_WithFilter(t *testing.T) { tests.AssertNoError(t, err) // Test GetAll with session filter - filter := testdata.CreateFilterWithSession(helper.TestData.ServerID.String(), "Race") + filter := testdata.CreateFilterWithSession(helper.TestData.ServerID.String(), model.SessionRace) result, err := repo.GetAll(ctx, filter) tests.AssertNoError(t, err) tests.AssertNotNil(t, result) tests.AssertEqual(t, 1, len(*result)) - tests.AssertEqual(t, "Race", (*result)[0].Session) + tests.AssertEqual(t, model.SessionRace, (*result)[0].Session) tests.AssertEqual(t, 15, (*result)[0].PlayerCount) } @@ -145,8 +145,8 @@ func TestStateHistoryRepository_GetLastSessionID_Success(t *testing.T) { sessionID1 := uuid.New() sessionID2 := uuid.New() - history1 := testData.CreateStateHistory("Practice", "spa", 5, sessionID1) - history2 := testData.CreateStateHistory("Race", "spa", 10, sessionID2) + history1 := testData.CreateStateHistory(model.SessionPractice, "spa", 5, sessionID1) + history2 := testData.CreateStateHistory(model.SessionRace, "spa", 10, sessionID2) // Insert with a small delay to ensure ordering err := repo.Insert(ctx, &history1) @@ -217,7 +217,7 @@ func TestStateHistoryRepository_GetSummaryStats_Success(t *testing.T) { sessionID2 := uuid.New() // Practice session: 5, 10, 15 players - practiceEntries := testData.CreateMultipleEntries("Practice", "spa", []int{5, 10, 15}) + practiceEntries := testData.CreateMultipleEntries(model.SessionPractice, "spa", []int{5, 10, 15}) for i := range practiceEntries { practiceEntries[i].SessionID = sessionID1 err := repo.Insert(ctx, &practiceEntries[i]) @@ -225,7 +225,7 @@ func TestStateHistoryRepository_GetSummaryStats_Success(t *testing.T) { } // Race session: 20, 25, 30 players - raceEntries := testData.CreateMultipleEntries("Race", "spa", []int{20, 25, 30}) + raceEntries := testData.CreateMultipleEntries(model.SessionRace, "spa", []int{20, 25, 30}) for i := range raceEntries { raceEntries[i].SessionID = sessionID2 err := repo.Insert(ctx, &raceEntries[i]) @@ -305,7 +305,7 @@ func TestStateHistoryRepository_GetTotalPlaytime_Success(t *testing.T) { { ID: uuid.New(), ServerID: helper.TestData.ServerID, - Session: "Practice", + Session: model.SessionPractice, Track: "spa", PlayerCount: 5, DateCreated: baseTime, @@ -316,7 +316,7 @@ func TestStateHistoryRepository_GetTotalPlaytime_Success(t *testing.T) { { ID: uuid.New(), ServerID: helper.TestData.ServerID, - Session: "Practice", + Session: model.SessionPractice, Track: "spa", PlayerCount: 10, DateCreated: baseTime.Add(30 * time.Minute), @@ -327,7 +327,7 @@ func TestStateHistoryRepository_GetTotalPlaytime_Success(t *testing.T) { { ID: uuid.New(), ServerID: helper.TestData.ServerID, - Session: "Practice", + Session: model.SessionPractice, Track: "spa", PlayerCount: 8, DateCreated: baseTime.Add(60 * time.Minute), @@ -391,7 +391,7 @@ func TestStateHistoryRepository_ConcurrentOperations(t *testing.T) { } // Create and insert initial entry to ensure table exists and is properly set up - initialHistory := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + initialHistory := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(ctx, &initialHistory) if err != nil { t.Fatalf("Failed to insert initial record: %v", err) @@ -404,7 +404,7 @@ func TestStateHistoryRepository_ConcurrentOperations(t *testing.T) { defer func() { done <- true }() - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(ctx, &history) if err != nil { t.Logf("Insert error: %v", err) @@ -462,7 +462,7 @@ func TestStateHistoryRepository_FilterEdgeCases(t *testing.T) { // Insert a test record to ensure the table is properly set up testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(ctx, &history) tests.AssertNoError(t, err) diff --git a/tests/unit/service/config_service_test.go b/tests/unit/service/config_service_test.go index 0f8d743..af378d0 100644 --- a/tests/unit/service/config_service_test.go +++ b/tests/unit/service/config_service_test.go @@ -94,7 +94,7 @@ func TestConfigService_GetEventConfig_ValidFile(t *testing.T) { // Verify sessions tests.AssertEqual(t, 3, len(eventConfig.Sessions)) if len(eventConfig.Sessions) > 0 { - tests.AssertEqual(t, "P", eventConfig.Sessions[0].SessionType) + tests.AssertEqual(t, model.SessionPractice, eventConfig.Sessions[0].SessionType) tests.AssertEqual(t, model.IntString(10), eventConfig.Sessions[0].SessionDurationMinutes) } } diff --git a/tests/unit/service/state_history_service_test.go b/tests/unit/service/state_history_service_test.go index 19e73d0..9a9fd7b 100644 --- a/tests/unit/service/state_history_service_test.go +++ b/tests/unit/service/state_history_service_test.go @@ -33,7 +33,7 @@ func TestStateHistoryService_GetAll_Success(t *testing.T) { // Insert test data directly into DB testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(helper.CreateContext(), &history) tests.AssertNoError(t, err) @@ -49,7 +49,7 @@ func TestStateHistoryService_GetAll_Success(t *testing.T) { tests.AssertNoError(t, err) tests.AssertNotNil(t, result) tests.AssertEqual(t, 1, len(*result)) - tests.AssertEqual(t, "Practice", (*result)[0].Session) + tests.AssertEqual(t, model.SessionPractice, (*result)[0].Session) tests.AssertEqual(t, 5, (*result)[0].PlayerCount) } @@ -71,8 +71,8 @@ func TestStateHistoryService_GetAll_WithFilter(t *testing.T) { // Insert test data with different sessions testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - practiceHistory := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) - raceHistory := testData.CreateStateHistory("Race", "spa", 10, uuid.New()) + practiceHistory := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) + raceHistory := testData.CreateStateHistory(model.SessionRace, "spa", 10, uuid.New()) err := repo.Insert(helper.CreateContext(), &practiceHistory) tests.AssertNoError(t, err) @@ -85,13 +85,13 @@ func TestStateHistoryService_GetAll_WithFilter(t *testing.T) { defer helper.ReleaseFiberCtx(app, ctx) // Test GetAll with session filter - filter := testdata.CreateFilterWithSession(helper.TestData.ServerID.String(), "Race") + filter := testdata.CreateFilterWithSession(helper.TestData.ServerID.String(), model.SessionRace) result, err := stateHistoryService.GetAll(ctx, filter) tests.AssertNoError(t, err) tests.AssertNotNil(t, result) tests.AssertEqual(t, 1, len(*result)) - tests.AssertEqual(t, "Race", (*result)[0].Session) + tests.AssertEqual(t, model.SessionRace, (*result)[0].Session) tests.AssertEqual(t, 10, (*result)[0].PlayerCount) } @@ -143,7 +143,7 @@ func TestStateHistoryService_Insert_Success(t *testing.T) { // Create test data testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) - history := testData.CreateStateHistory("Practice", "spa", 5, uuid.New()) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) // Create proper Fiber context app := fiber.New() @@ -180,7 +180,7 @@ func TestStateHistoryService_GetLastSessionID_Success(t *testing.T) { // Insert test data testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) sessionID := uuid.New() - history := testData.CreateStateHistory("Practice", "spa", 5, sessionID) + history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, sessionID) err := repo.Insert(helper.CreateContext(), &history) tests.AssertNoError(t, err) @@ -254,7 +254,7 @@ func TestStateHistoryService_GetStatistics_Success(t *testing.T) { { ID: uuid.New(), ServerID: helper.TestData.ServerID, - Session: "Practice", + Session: model.SessionPractice, Track: "spa", PlayerCount: 5, DateCreated: baseTime, @@ -265,7 +265,7 @@ func TestStateHistoryService_GetStatistics_Success(t *testing.T) { { ID: uuid.New(), ServerID: helper.TestData.ServerID, - Session: "Practice", + Session: model.SessionPractice, Track: "spa", PlayerCount: 10, DateCreated: baseTime.Add(5 * time.Minute), @@ -276,7 +276,7 @@ func TestStateHistoryService_GetStatistics_Success(t *testing.T) { { ID: uuid.New(), ServerID: helper.TestData.ServerID, - Session: "Race", + Session: model.SessionRace, Track: "spa", PlayerCount: 15, DateCreated: baseTime.Add(10 * time.Minute), @@ -404,7 +404,7 @@ func TestStateHistoryService_LogParsingWorkflow(t *testing.T) { } // Verify session changes were parsed correctly - expectedSessions := []string{"PRACTICE", "QUALIFY", "RACE", "NONE"} + expectedSessions := []model.TrackSession{model.SessionPractice, model.SessionQualify, model.SessionRace} sessionIndex := 0 for _, state := range stateChanges { @@ -433,7 +433,7 @@ func TestStateHistoryService_SessionChangeTracking(t *testing.T) { Name: "Test Server", } - var sessionChanges []string + var sessionChanges []model.TrackSession onStateChange := func(state *model.ServerState, changes ...tracking.StateChange) { for _, change := range changes { if change == tracking.Session { @@ -448,7 +448,7 @@ func TestStateHistoryService_SessionChangeTracking(t *testing.T) { // We'll add one session change at a time and wait briefly to ensure they're processed in order for _, expected := range testdata.ExpectedSessionChanges { - line := "[2024-01-15 14:30:25.123] Session changed: " + expected.From + " -> " + expected.To + line := string("[2024-01-15 14:30:25.123] Session changed: " + expected.From + " -> " + expected.To) instance.HandleLogLine(line) // Small pause to ensure log processing completes time.Sleep(10 * time.Millisecond)