package service import ( "errors" "testing" "time" "browser.local/platform/domain" ) func TestSCUMFactIngestMaintainsPlatformTablesAndWelcomeQueue(t *testing.T) { svc, session, runSession, instance := newSourceRCONFixture(t) squadID := int64(42) bank := int64(1200) gold := int64(3) exists := true locked := true functional := true observedAt := fixedTime.Add(-1 * time.Minute) result, err := svc.IngestSCUMFacts(domain.SCUMFactIngest{ RunEndpointID: instance.RunEndpointID, SessionToken: runSession, ServerInstanceID: instance.ID, Users: []domain.SCUMUserFact{{ SteamID: "76561198000000001", DisplayName: "Mira", SquadID: &squadID, LoginIP: "203.0.113.7", Online: true, Login: true, ObservedAt: observedAt, LoginObservedAt: observedAt, Position: &domain.SCUMPosition{X: 10, Y: 20, Z: 3}, BankBalance: &bank, GoldBars: &gold, RiddenGameVehicleID: "game-veh-1", }}, Vehicles: []domain.SCUMVehicleFact{{ GameVehicleID: "game-veh-1", VehicleClass: "BPC_Laika_C", DisplayName: "Laika", Exists: &exists, Locked: &locked, Functional: &functional, LockedBySteamID: "76561198000000001", LockedAt: observedAt, ObservedAt: observedAt, Position: &domain.SCUMPosition{X: 400, Y: 200, Z: 0}, }}, }) if err != nil { t.Fatalf("ingest SCUM facts: %v", err) } if !result.Accepted || result.AcceptedUserCount != 1 || result.AcceptedVehicleCount != 1 || result.WelcomeQueuedCount != 1 { t.Fatalf("unexpected ingest result: %+v", result) } users, err := svc.ListSCUMUsersForSession(session, domain.SCUMUserFilter{ServerInstanceID: instance.ID}) if err != nil || len(users) != 1 { t.Fatalf("list SCUM users: users=%+v err=%v", users, err) } user := users[0] if user.SteamID != "76561198000000001" || user.SquadID != squadID || user.LastLoginIP != "203.0.113.7" || !user.Online || user.BankBalance == nil || *user.BankBalance != bank || user.GoldBars == nil || *user.GoldBars != gold || user.RiddenVehicleID == "" || user.GameVehicleID != "game-veh-1" { t.Fatalf("unexpected SCUM user: %+v", user) } if user.LastLoginAge != time.Minute { t.Fatalf("expected one-minute login age, got %s", user.LastLoginAge) } jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: instance.ID}) if err != nil || len(jobs) != 1 { t.Fatalf("expected one queued welcome job, jobs=%+v err=%v", jobs, err) } if jobs[0].Capability != domain.JobCapabilityRemoteRunRCONCommand || !jobs[0].QueueEligibleAt.Equal(fixedTime.Add(domain.SCUMWelcomeMessageDelay)) { t.Fatalf("unexpected welcome job: %+v", jobs[0]) } userTracks, err := svc.ListSCUMUserTrajectoriesForSession(session, domain.SCUMUserTrajectoryFilter{ServerInstanceID: instance.ID}) if err != nil || len(userTracks) != 1 || userTracks[0].RiddenVehicleID != user.RiddenVehicleID { t.Fatalf("unexpected user trajectory rows: rows=%+v err=%v", userTracks, err) } vehicles, err := svc.ListSCUMVehiclesForSession(session, domain.SCUMVehicleFilter{ServerInstanceID: instance.ID}) if err != nil || len(vehicles) != 1 || vehicles[0].GameVehicleID != "game-veh-1" || !vehicles[0].Exists || !vehicles[0].Locked || vehicles[0].Functional == nil || !*vehicles[0].Functional { t.Fatalf("unexpected SCUM vehicles: vehicles=%+v err=%v", vehicles, err) } vehicleTracks, err := svc.ListSCUMVehicleTrajectoriesForSession(session, domain.SCUMVehicleTrajectoryFilter{ServerInstanceID: instance.ID}) if err != nil || len(vehicleTracks) != 1 || vehicleTracks[0].SCUMVehicleID != vehicles[0].ID { t.Fatalf("unexpected vehicle trajectory rows: rows=%+v err=%v", vehicleTracks, err) } locks, err := svc.ListSCUMVehicleLocksForSession(session, domain.SCUMVehicleLockFilter{ServerInstanceID: instance.ID}) if err != nil || len(locks) != 1 || locks[0].SCUMUserID != user.ID || locks[0].SCUMVehicleID != vehicles[0].ID { t.Fatalf("unexpected vehicle lock history: locks=%+v err=%v", locks, err) } _, err = svc.IngestSCUMFacts(domain.SCUMFactIngest{ RunEndpointID: instance.RunEndpointID, SessionToken: runSession, ServerInstanceID: instance.ID, Users: []domain.SCUMUserFact{{SteamID: "76561198000000001", Online: true, ObservedAt: observedAt, Position: &domain.SCUMPosition{X: 10, Y: 20, Z: 3}}}, Vehicles: []domain.SCUMVehicleFact{{GameVehicleID: "game-veh-1", ObservedAt: observedAt, Position: &domain.SCUMPosition{X: 400, Y: 200, Z: 0}}}, }) if err != nil { t.Fatalf("re-ingest unchanged SCUM facts: %v", err) } userTracks, _ = svc.ListSCUMUserTrajectoriesForSession(session, domain.SCUMUserTrajectoryFilter{ServerInstanceID: instance.ID}) vehicleTracks, _ = svc.ListSCUMVehicleTrajectoriesForSession(session, domain.SCUMVehicleTrajectoryFilter{ServerInstanceID: instance.ID}) if len(userTracks) != 1 || len(vehicleTracks) != 1 { t.Fatalf("unchanged positions must not create trajectory rows, users=%+v vehicles=%+v", userTracks, vehicleTracks) } _, err = svc.IngestSCUMFacts(domain.SCUMFactIngest{ RunEndpointID: instance.RunEndpointID, SessionToken: runSession, ServerInstanceID: instance.ID, Users: []domain.SCUMUserFact{{SteamID: "76561198000000001", Online: false, ObservedAt: fixedTime, Position: &domain.SCUMPosition{X: 11, Y: 21, Z: 4}}}, }) if err != nil { t.Fatalf("ingest offline SCUM user fact: %v", err) } userTracks, _ = svc.ListSCUMUserTrajectoriesForSession(session, domain.SCUMUserTrajectoryFilter{ServerInstanceID: instance.ID}) if len(userTracks) != 1 { t.Fatalf("offline users must not create trajectory rows, rows=%+v", userTracks) } } func TestSCUMFactIngestQueuesFreshWelcomeAfterActivityGap(t *testing.T) { svc, session, runSession, instance := newSourceRCONFixture(t) firstLogin := fixedTime.Add(-20 * time.Minute) secondLogin := fixedTime.Add(-4 * time.Minute) for _, loginAt := range []time.Time{firstLogin, secondLogin} { result, err := svc.IngestSCUMFacts(domain.SCUMFactIngest{RunEndpointID: instance.RunEndpointID, SessionToken: runSession, ServerInstanceID: instance.ID, Users: []domain.SCUMUserFact{{SteamID: "76561198000000002", DisplayName: "Noah", Online: true, Login: true, ObservedAt: loginAt, LoginObservedAt: loginAt}}}) if err != nil { t.Fatalf("ingest login at %s: %v", loginAt, err) } if result.WelcomeQueuedCount != 1 { t.Fatalf("expected fresh welcome for login at %s, got %+v", loginAt, result) } } users, err := svc.ListSCUMUsersForSession(session, domain.SCUMUserFilter{ServerInstanceID: instance.ID, SteamID: "76561198000000002"}) if err != nil || len(users) != 1 { t.Fatalf("list gap user: users=%+v err=%v", users, err) } if users[0].LastLoginAge != 4*time.Minute { t.Fatalf("expected last login age to show the second login, got %s", users[0].LastLoginAge) } jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: instance.ID}) if err != nil || len(jobs) != 2 { t.Fatalf("expected two welcome jobs after activity gap, jobs=%+v err=%v", jobs, err) } } func TestSCUMStaleOnlineUserConvergesToOffline(t *testing.T) { svc, session, runSession, instance := newSourceRCONFixture(t) staleAt := fixedTime.Add(-16 * time.Minute) if _, err := svc.IngestSCUMFacts(domain.SCUMFactIngest{ RunEndpointID: instance.RunEndpointID, SessionToken: runSession, ServerInstanceID: instance.ID, Users: []domain.SCUMUserFact{{SteamID: "76561198000000003", DisplayName: "Stale", Online: true, Login: true, ObservedAt: staleAt, LoginObservedAt: staleAt}}, }); err != nil { t.Fatalf("ingest stale SCUM user fact: %v", err) } users, err := svc.ListSCUMUsersForSession(session, domain.SCUMUserFilter{ServerInstanceID: instance.ID}) if err != nil || len(users) != 1 { t.Fatalf("list stale SCUM users: users=%+v err=%v", users, err) } if users[0].Online { t.Fatalf("expected a user without activity for %s to converge offline: %+v", domain.SCUMUserOfflineAfter, users[0]) } } func TestSCUMFactsRejectedForNonSCUMServerInstance(t *testing.T) { svc, _, runSession, instance := newSourceRCONFixture(t) if err := svc.store.GamePlugins().Create(domain.GamePlugin{ID: "server.other", Name: "Other", Version: "1.0.0", ServerType: "other"}); err != nil { t.Fatalf("create non-SCUM plugin: %v", err) } if err := svc.store.ServerInstances().Create(domain.ServerInstance{ID: "server-other", PluginID: "server.other", RunEndpointID: instance.RunEndpointID, Name: "Other Server", State: domain.ServerInstanceStateRunning, ConfigVersion: 1}); err != nil { t.Fatalf("create non-SCUM server: %v", err) } _, err := svc.IngestSCUMFacts(domain.SCUMFactIngest{ RunEndpointID: instance.RunEndpointID, SessionToken: runSession, ServerInstanceID: "server-other", Users: []domain.SCUMUserFact{{SteamID: "76561198000000004", Online: true}}, }) if !errors.Is(err, ErrForbidden) { t.Fatalf("expected SCUM facts for a non-SCUM server to be rejected, got %v", err) } }