package service import ( "acc-server-manager/local/model" "acc-server-manager/local/repository" "acc-server-manager/local/service" "acc-server-manager/local/utl/tracking" "acc-server-manager/tests" "acc-server-manager/tests/testdata" "testing" "time" "github.com/gofiber/fiber/v2" "github.com/google/uuid" ) func TestStateHistoryService_GetAll_Success(t *testing.T) { helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) err := repo.Insert(helper.CreateContext(), &history) tests.AssertNoError(t, err) app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) filter := testdata.CreateBasicFilter(helper.TestData.ServerID.String()) result, err := stateHistoryService.GetAll(ctx, filter) tests.AssertNoError(t, err) tests.AssertNotNil(t, result) tests.AssertEqual(t, 1, len(*result)) tests.AssertEqual(t, model.SessionPractice, (*result)[0].Session) tests.AssertEqual(t, 5, (*result)[0].PlayerCount) } func TestStateHistoryService_GetAll_WithFilter(t *testing.T) { helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) 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) err = repo.Insert(helper.CreateContext(), &raceHistory) tests.AssertNoError(t, err) app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) 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, model.SessionRace, (*result)[0].Session) tests.AssertEqual(t, 10, (*result)[0].PlayerCount) } func TestStateHistoryService_GetAll_NoData(t *testing.T) { helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) filter := testdata.CreateBasicFilter(helper.TestData.ServerID.String()) result, err := stateHistoryService.GetAll(ctx, filter) tests.AssertNoError(t, err) tests.AssertNotNil(t, result) tests.AssertEqual(t, 0, len(*result)) } func TestStateHistoryService_Insert_Success(t *testing.T) { helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, uuid.New()) app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) err := stateHistoryService.Insert(ctx, &history) tests.AssertNoError(t, err) filter := testdata.CreateBasicFilter(helper.TestData.ServerID.String()) result, err := stateHistoryService.GetAll(ctx, filter) tests.AssertNoError(t, err) tests.AssertEqual(t, 1, len(*result)) } func TestStateHistoryService_GetLastSessionID_Success(t *testing.T) { helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) testData := testdata.NewStateHistoryTestData(helper.TestData.ServerID) sessionID := uuid.New() history := testData.CreateStateHistory(model.SessionPractice, "spa", 5, sessionID) err := repo.Insert(helper.CreateContext(), &history) tests.AssertNoError(t, err) app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) lastSessionID, err := stateHistoryService.GetLastSessionID(ctx, helper.TestData.ServerID) tests.AssertNoError(t, err) tests.AssertEqual(t, sessionID, lastSessionID) } func TestStateHistoryService_GetLastSessionID_NoData(t *testing.T) { helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) lastSessionID, err := stateHistoryService.GetLastSessionID(ctx, helper.TestData.ServerID) tests.AssertNoError(t, err) tests.AssertEqual(t, uuid.Nil, lastSessionID) } func TestStateHistoryService_GetStatistics_Success(t *testing.T) { t.Skip("Skipping test as it's dependent on database migration") helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) _ = testdata.NewStateHistoryTestData(helper.TestData.ServerID) sessionID1 := uuid.New() sessionID2 := uuid.New() baseTime := time.Now().UTC() entries := []model.StateHistory{ { ID: uuid.New(), ServerID: helper.TestData.ServerID, Session: model.SessionPractice, Track: "spa", PlayerCount: 5, DateCreated: baseTime, SessionStart: baseTime, SessionDurationMinutes: 30, SessionID: sessionID1, }, { ID: uuid.New(), ServerID: helper.TestData.ServerID, Session: model.SessionPractice, Track: "spa", PlayerCount: 10, DateCreated: baseTime.Add(5 * time.Minute), SessionStart: baseTime, SessionDurationMinutes: 30, SessionID: sessionID1, }, { ID: uuid.New(), ServerID: helper.TestData.ServerID, Session: model.SessionRace, Track: "spa", PlayerCount: 15, DateCreated: baseTime.Add(10 * time.Minute), SessionStart: baseTime.Add(10 * time.Minute), SessionDurationMinutes: 45, SessionID: sessionID2, }, } for _, entry := range entries { err := repo.Insert(helper.CreateContext(), &entry) tests.AssertNoError(t, err) } app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) filter := &model.StateHistoryFilter{ ServerBasedFilter: model.ServerBasedFilter{ ServerID: helper.TestData.ServerID.String(), }, DateRangeFilter: model.DateRangeFilter{ StartDate: baseTime.Add(-1 * time.Hour), EndDate: baseTime.Add(1 * time.Hour), }, } stats, err := stateHistoryService.GetStatistics(ctx, filter) tests.AssertNoError(t, err) tests.AssertNotNil(t, stats) tests.AssertEqual(t, 15, stats.PeakPlayers) tests.AssertEqual(t, 2, stats.TotalSessions) expectedAverage := float64(5+10+15) / 3.0 if stats.AveragePlayers != expectedAverage { t.Errorf("Expected average players %.1f, got %.1f", expectedAverage, stats.AveragePlayers) } tests.AssertNotNil(t, stats.PlayerCountOverTime) tests.AssertNotNil(t, stats.SessionTypes) tests.AssertNotNil(t, stats.DailyActivity) tests.AssertNotNil(t, stats.RecentSessions) } func TestStateHistoryService_GetStatistics_NoData(t *testing.T) { t.Skip("Skipping test as it's dependent on database migration") helper := tests.NewTestHelper(t) defer helper.Cleanup() if !helper.DB.Migrator().HasTable(&model.StateHistory{}) { err := helper.DB.Migrator().CreateTable(&model.StateHistory{}) if err != nil { t.Fatalf("Failed to create state_histories table: %v", err) } } repo := repository.NewStateHistoryRepository(helper.DB) stateHistoryService := service.NewStateHistoryService(repo) app := fiber.New() ctx := helper.CreateFiberCtx() defer helper.ReleaseFiberCtx(app, ctx) filter := testdata.CreateBasicFilter(helper.TestData.ServerID.String()) stats, err := stateHistoryService.GetStatistics(ctx, filter) tests.AssertNoError(t, err) tests.AssertNotNil(t, stats) tests.AssertEqual(t, 0, stats.PeakPlayers) tests.AssertEqual(t, 0.0, stats.AveragePlayers) tests.AssertEqual(t, 0, stats.TotalSessions) tests.AssertEqual(t, 0, stats.TotalPlaytime) } func TestStateHistoryService_LogParsingWorkflow(t *testing.T) { t.Skip("Skipping log parsing test as it's not critical to the service functionality") helper := tests.NewTestHelper(t) defer helper.Cleanup() err := helper.InsertTestServer() tests.AssertNoError(t, err) server := helper.TestData.Server var stateChanges []*model.ServerState onStateChange := func(state *model.ServerState, changes ...tracking.StateChange) { stateChanges = append(stateChanges, state) } instance := tracking.NewAccServerInstance(server, onStateChange) logLines := testdata.SampleLogLines for _, line := range logLines { instance.HandleLogLine(line) } if len(stateChanges) == 0 { t.Error("Expected state changes from log parsing, got none") } expectedSessions := []model.TrackSession{model.SessionPractice, model.SessionQualify, model.SessionRace} sessionIndex := 0 for _, state := range stateChanges { if state.Session != "" && sessionIndex < len(expectedSessions) { if state.Session != expectedSessions[sessionIndex] { t.Errorf("Expected session %s, got %s", expectedSessions[sessionIndex], state.Session) } sessionIndex++ } } if len(stateChanges) > 0 { finalState := stateChanges[len(stateChanges)-1] tests.AssertEqual(t, 0, finalState.PlayerCount) } } func TestStateHistoryService_SessionChangeTracking(t *testing.T) { t.Skip("Skipping session tracking test as it's unreliable in CI environments") server := &model.Server{ ID: uuid.New(), Name: "Test Server", } var sessionChanges []model.TrackSession onStateChange := func(state *model.ServerState, changes ...tracking.StateChange) { for _, change := range changes { if change == tracking.Session { sessionCopy := state.Session sessionChanges = append(sessionChanges, sessionCopy) } } } instance := tracking.NewAccServerInstance(server, onStateChange) for _, expected := range testdata.ExpectedSessionChanges { line := string("[2024-01-15 14:30:25.123] Session changed: " + expected.From + " -> " + expected.To) instance.HandleLogLine(line) time.Sleep(10 * time.Millisecond) } if len(sessionChanges) == 0 { t.Error("No session changes detected") return } lastExpected := testdata.ExpectedSessionChanges[len(testdata.ExpectedSessionChanges)-1].To lastActual := sessionChanges[len(sessionChanges)-1] if lastActual != lastExpected { t.Errorf("Last session should be %s, got %s", lastExpected, lastActual) } } func TestStateHistoryService_PlayerCountTracking(t *testing.T) { t.Skip("Skipping player count tracking test as it's unreliable in CI environments") server := &model.Server{ ID: uuid.New(), Name: "Test Server", } var playerCounts []int onStateChange := func(state *model.ServerState, changes ...tracking.StateChange) { for _, change := range changes { if change == tracking.PlayerCount { playerCounts = append(playerCounts, state.PlayerCount) } } } instance := tracking.NewAccServerInstance(server, onStateChange) expectedCounts := testdata.ExpectedPlayerCounts logLines := []string{ "[2024-01-15 14:30:30.456] 1 client(s) online", "[2024-01-15 14:30:35.789] 3 client(s) online", "[2024-01-15 14:31:00.123] 5 client(s) online", "[2024-01-15 14:35:05.789] 8 client(s) online", "[2024-01-15 14:40:05.456] 12 client(s) online", "[2024-01-15 14:45:00.789] 15 client(s) online", "[2024-01-15 14:50:00.789] Removing dead connection", "[2024-01-15 15:00:00.789] 0 client(s) online", } for _, line := range logLines { instance.HandleLogLine(line) } tests.AssertEqual(t, len(expectedCounts), len(playerCounts)) for i, expected := range expectedCounts { if i < len(playerCounts) { tests.AssertEqual(t, expected, playerCounts[i]) } } } func TestStateHistoryService_EdgeCases(t *testing.T) { t.Skip("Skipping edge cases test as it's unreliable in CI environments") server := &model.Server{ ID: uuid.New(), Name: "Test Server", } var stateChanges []*model.ServerState onStateChange := func(state *model.ServerState, changes ...tracking.StateChange) { stateCopy := *state stateChanges = append(stateChanges, &stateCopy) } instance := tracking.NewAccServerInstance(server, onStateChange) edgeCaseLines := []string{ "[2024-01-15 14:30:25.123] Some unrelated log line", "[2024-01-15 14:30:25.123] Session changed: NONE -> PRACTICE", "[2024-01-15 14:30:30.456] 0 client(s) online", "[2024-01-15 14:30:35.789] -1 client(s) online", "[2024-01-15 14:30:40.789] 30 client(s) online", "[2024-01-15 14:30:45.789] invalid client(s) online", } for _, line := range edgeCaseLines { instance.HandleLogLine(line) } if len(stateChanges) == 0 { t.Errorf("Expected state changes, got none") return } found30Players := false for _, state := range stateChanges { if state.PlayerCount == 30 { found30Players = true break } } if !found30Players { t.Log("Player counts in recorded states:") for i, state := range stateChanges { t.Logf("State %d: PlayerCount=%d", i, state.PlayerCount) } t.Error("Expected to find state with 30 players") } } func TestStateHistoryService_SessionStartTracking(t *testing.T) { t.Skip("Skipping session start tracking test as it's unreliable in CI environments") server := &model.Server{ ID: uuid.New(), Name: "Test Server", } var sessionStarts []time.Time onStateChange := func(state *model.ServerState, changes ...tracking.StateChange) { for _, change := range changes { if change == tracking.Session && !state.SessionStart.IsZero() { sessionStarts = append(sessionStarts, state.SessionStart) } } } instance := tracking.NewAccServerInstance(server, onStateChange) startTime := time.Now() instance.HandleLogLine("[2024-01-15 14:30:30.456] 1 client(s) online") if len(sessionStarts) == 0 { t.Error("Expected session start to be recorded when first player joins") } if len(sessionStarts) > 0 { timeDiff := sessionStarts[0].Sub(startTime) if timeDiff > time.Second || timeDiff < -time.Second { t.Errorf("Session start time seems incorrect, diff: %v", timeDiff) } } }