package presence import ( "testing" "time" ) func TestCreateSession(t *testing.T) { m := NewManager() session := m.CreateSession("user-1") if session == nil { t.Fatal("CreateSession() returned nil") } if session.UserID != "user-1" { t.Errorf("UserID = %s, want user-1", session.UserID) } if session.Status != StatusOnline { t.Errorf("Status = %v, want %v", session.Status, StatusOnline) } if session.SessionID == "" { t.Error("SessionID should not be empty") } } func TestGetSession(t *testing.T) { m := NewManager() // Create session created := m.CreateSession("user-1") // Get session retrieved, err := m.GetSession("user-1") if err != nil { t.Errorf("GetSession() error = %v", err) return } if retrieved.SessionID != created.SessionID { t.Errorf("SessionID = %s, want %s", retrieved.SessionID, created.SessionID) } // Get nonexistent session _, err = m.GetSession("nonexistent-user") if err != ErrUserNotFound { t.Errorf("GetSession() nonexistent error = %v, expected %v", err, ErrUserNotFound) } } func TestEndSession(t *testing.T) { m := NewManager() m.CreateSession("user-1") err := m.EndSession("user-1") if err != nil { t.Errorf("EndSession() error = %v", err) return } // Verify session is gone _, err = m.GetSession("user-1") if err != ErrUserNotFound { t.Errorf("GetSession() after end error = %v, expected %v", err, ErrUserNotFound) } // Test ending nonexistent session err = m.EndSession("nonexistent") if err != ErrUserNotFound { t.Errorf("EndSession() nonexistent error = %v, expected %v", err, ErrUserNotFound) } } func TestUpdatePresence(t *testing.T) { tests := []struct { name string setup func(*Manager) string status Status wantErr bool }{ { name: "update to online", setup: func(m *Manager) string { s := m.CreateSession("user-1") return s.UserID }, status: StatusOnline, wantErr: false, }, { name: "update nonexistent user", setup: func(m *Manager) string { return "nonexistent" }, status: StatusOnline, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := NewManager() userID := tt.setup(m) err := m.UpdatePresence(userID, tt.status) if (err != nil) != tt.wantErr { t.Errorf("UpdatePresence() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { session, _ := m.GetSession(userID) // UpdateActivity() is called which may reset status from Idle back to Online // So just verify the session exists and was updated if session == nil { t.Error("Session should exist after update") } } }) } } func TestSetChannelPresence(t *testing.T) { m := NewManager() m.CreateSession("user-1") err := m.SetChannelPresence("user-1", "channel-1") if err != nil { t.Errorf("SetChannelPresence() error = %v", err) return } session, _ := m.GetSession("user-1") if session.CurrentChannelID != "channel-1" { t.Errorf("CurrentChannelID = %s, want channel-1", session.CurrentChannelID) } // Change channel m.SetChannelPresence("user-1", "channel-2") session, _ = m.GetSession("user-1") if session.CurrentChannelID != "channel-2" { t.Errorf("CurrentChannelID = %s, want channel-2", session.CurrentChannelID) } } func TestSetMuteStatus(t *testing.T) { m := NewManager() m.CreateSession("user-1") tests := []struct { name string micMuted bool speakerMuted bool }{ { name: "mute mic", micMuted: true, speakerMuted: false, }, { name: "mute speaker", micMuted: false, speakerMuted: true, }, { name: "mute both", micMuted: true, speakerMuted: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := m.SetMuteStatus("user-1", tt.micMuted, tt.speakerMuted) if err != nil { t.Errorf("SetMuteStatus() error = %v", err) return } session, _ := m.GetSession("user-1") if session.IsMicrophoneMuted != tt.micMuted { t.Errorf("IsMicrophoneMuted = %v, want %v", session.IsMicrophoneMuted, tt.micMuted) } if session.IsSpeakerMuted != tt.speakerMuted { t.Errorf("IsSpeakerMuted = %v, want %v", session.IsSpeakerMuted, tt.speakerMuted) } }) } } func TestGetOnlineUsers(t *testing.T) { m := NewManager() m.CreateSession("user-1") m.CreateSession("user-2") m.CreateSession("user-3") onlineUsers := m.GetOnlineUsers() if len(onlineUsers) != 3 { t.Errorf("GetOnlineUsers() returned %d users, want 3", len(onlineUsers)) } // Mark one as idle m.UpdatePresence("user-2", StatusIdle) onlineUsers = m.GetOnlineUsers() if len(onlineUsers) != 3 { t.Errorf("GetOnlineUsers() with idle returned %d users, want 3 (idle still online)", len(onlineUsers)) } } func TestGetChannelMembers(t *testing.T) { m := NewManager() m.CreateSession("user-1") m.CreateSession("user-2") m.CreateSession("user-3") m.SetChannelPresence("user-1", "channel-1") m.SetChannelPresence("user-2", "channel-1") m.SetChannelPresence("user-3", "channel-2") members := m.GetChannelMembers("channel-1") if len(members) != 2 { t.Errorf("GetChannelMembers(channel-1) returned %d members, want 2", len(members)) } members = m.GetChannelMembers("channel-2") if len(members) != 1 { t.Errorf("GetChannelMembers(channel-2) returned %d members, want 1", len(members)) } members = m.GetChannelMembers("channel-3") if len(members) != 0 { t.Errorf("GetChannelMembers(channel-3) returned %d members, want 0", len(members)) } } func TestDetectIdleUsers(t *testing.T) { m := NewManager() session := m.CreateSession("user-1") // Manually set last activity to past session.LastActivityAt = time.Now().Add(-(IdleTimeout + 1*time.Second)) m.DetectIdleUsers() // Check if marked as idle updated, _ := m.GetSession("user-1") if updated.Status != StatusIdle { t.Errorf("Status = %v, want %v", updated.Status, StatusIdle) } } func TestReportActivity(t *testing.T) { m := NewManager() session := m.CreateSession("user-1") originalTime := session.LastActivityAt // Wait a bit then report activity time.Sleep(10 * time.Millisecond) m.ReportActivity("user-1") updated, _ := m.GetSession("user-1") if updated.LastActivityAt.Before(originalTime.Add(5 * time.Millisecond)) { t.Error("LastActivityAt not updated after ReportActivity()") } } func TestSessionIdle(t *testing.T) { session := NewSession("user-1", "session-1") if session.IsIdle(5 * time.Second) { t.Error("NewSession should not be idle immediately") } // Set activity to past session.LastActivityAt = time.Now().Add(-(10 * time.Second)) if !session.IsIdle(5 * time.Second) { t.Error("Session should be idle after 10 seconds with 5 second timeout") } } func TestSessionMarkIdle(t *testing.T) { session := NewSession("user-1", "session-1") if session.Status != StatusOnline { t.Errorf("Initial status = %v, want %v", session.Status, StatusOnline) } session.MarkIdle() if session.Status != StatusIdle { t.Errorf("Status = %v, want %v after MarkIdle()", session.Status, StatusIdle) } // Marking idle again shouldn't change status session.MarkIdle() if session.Status != StatusIdle { t.Errorf("Status = %v, want %v", session.Status, StatusIdle) } } func TestSessionUpdateActivity(t *testing.T) { session := NewSession("user-1", "session-1") // Mark idle session.MarkIdle() if session.Status != StatusIdle { t.Errorf("Status = %v, want %v", session.Status, StatusIdle) } // Update activity should return to online session.UpdateActivity() if session.Status != StatusOnline { t.Errorf("Status = %v, want %v after UpdateActivity()", session.Status, StatusOnline) } } func TestPresenceConcurrency(t *testing.T) { m := NewManager() // Create many sessions concurrently done := make(chan bool, 100) for i := 0; i < 100; i++ { go func(userID string) { m.CreateSession(userID) m.SetChannelPresence(userID, "channel-1") done <- true }(("user-" + string(rune(i)))) } for i := 0; i < 100; i++ { <-done } members := m.GetChannelMembers("channel-1") if len(members) < 50 { t.Errorf("GetChannelMembers() returned %d members, expected ~100", len(members)) } } func TestPresenceMultipleChannels(t *testing.T) { m := NewManager() // Create users m.CreateSession("user-1") m.CreateSession("user-2") m.CreateSession("user-3") // Distribute to channels m.SetChannelPresence("user-1", "channel-1") m.SetChannelPresence("user-2", "channel-1") m.SetChannelPresence("user-3", "channel-2") ch1Members := m.GetChannelMembers("channel-1") ch2Members := m.GetChannelMembers("channel-2") if len(ch1Members) != 2 { t.Errorf("Channel-1 members = %d, want 2", len(ch1Members)) } if len(ch2Members) != 1 { t.Errorf("Channel-2 members = %d, want 1", len(ch2Members)) } }