Spaces:
Paused
Paused
| import pytest | |
| import time | |
| from unittest.mock import patch | |
| from classes.session_tracker import SessionTracker | |
| from constants import FOUR_HOURS | |
| class TestSessionTracker: | |
| """Unit tests for SessionTracker class""" | |
| def tracker(self): | |
| """Fresh tracker instance for each test""" | |
| return SessionTracker() | |
| def sample_session_id(self): | |
| return "test-session-123" | |
| # ==================== update_session Tests ==================== | |
| def test_update_session_new_session(self, tracker, sample_session_id): | |
| """Test updating a new session creates an entry with current timestamp""" | |
| before = time.time() | |
| tracker.update_session(sample_session_id) | |
| after = time.time() | |
| assert sample_session_id in tracker.session_timestamp_map | |
| timestamp = tracker.session_timestamp_map[sample_session_id] | |
| assert before <= timestamp <= after | |
| def test_update_session_existing_session(self, tracker, sample_session_id): | |
| """Test updating an existing session updates its timestamp""" | |
| # First update | |
| tracker.update_session(sample_session_id) | |
| first_timestamp = tracker.session_timestamp_map[sample_session_id] | |
| # Wait a bit | |
| time.sleep(0.01) | |
| # Second update | |
| tracker.update_session(sample_session_id) | |
| second_timestamp = tracker.session_timestamp_map[sample_session_id] | |
| assert second_timestamp > first_timestamp | |
| def test_update_session_multiple_sessions(self, tracker): | |
| """Test updating multiple different sessions""" | |
| sessions = ["session-1", "session-2", "session-3"] | |
| for session_id in sessions: | |
| tracker.update_session(session_id) | |
| assert len(tracker.session_timestamp_map) == 3 | |
| for session_id in sessions: | |
| assert session_id in tracker.session_timestamp_map | |
| def test_update_session_stores_exact_timestamp( | |
| self, mock_time, tracker, sample_session_id | |
| ): | |
| """Test that update_session stores the exact timestamp from time.time()""" | |
| mock_time.return_value = 1234567890.123 | |
| tracker.update_session(sample_session_id) | |
| assert tracker.session_timestamp_map[sample_session_id] == 1234567890.123 | |
| # ==================== delete_session Tests ==================== | |
| def test_delete_session_existing_session(self, tracker, sample_session_id): | |
| """Test deleting an existing session""" | |
| tracker.update_session(sample_session_id) | |
| tracker.delete_session(sample_session_id) | |
| assert sample_session_id not in tracker.session_timestamp_map | |
| def test_delete_session_nonexistent_session(self, tracker): | |
| """Test deleting a nonexistent session (should not raise error)""" | |
| # Should not raise any exception | |
| tracker.delete_session("nonexistent-session") | |
| assert "nonexistent-session" not in tracker.session_timestamp_map | |
| def test_delete_session_does_not_affect_other_sessions(self, tracker): | |
| """Test that deleting one session doesn't affect others""" | |
| tracker.update_session("session-1") | |
| tracker.update_session("session-2") | |
| tracker.update_session("session-3") | |
| tracker.delete_session("session-2") | |
| assert "session-1" in tracker.session_timestamp_map | |
| assert "session-2" not in tracker.session_timestamp_map | |
| assert "session-3" in tracker.session_timestamp_map | |
| def test_delete_session_multiple_times(self, tracker, sample_session_id): | |
| """Test deleting the same session multiple times""" | |
| tracker.update_session(sample_session_id) | |
| tracker.delete_session(sample_session_id) | |
| tracker.delete_session(sample_session_id) # Should not raise error | |
| assert sample_session_id not in tracker.session_timestamp_map | |
| # ==================== delete_inactive_sessions Tests ==================== | |
| def test_delete_inactive_sessions_no_inactive(self, mock_time, tracker): | |
| """Test when all sessions are active (within FOUR_HOURS)""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session-1") | |
| tracker.update_session("session-2") | |
| # Move time forward but less than FOUR_HOURS | |
| mock_time.return_value = 1000.0 + FOUR_HOURS - 100 | |
| deleted = tracker.delete_inactive_sessions() | |
| assert deleted == [] | |
| assert len(tracker.session_timestamp_map) == 2 | |
| def test_delete_inactive_sessions_all_inactive(self, mock_time, tracker): | |
| """Test when all sessions are inactive (older than FOUR_HOURS)""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session-1") | |
| tracker.update_session("session-2") | |
| tracker.update_session("session-3") | |
| # Move time forward beyond FOUR_HOURS | |
| mock_time.return_value = 1000.0 + FOUR_HOURS + 100 | |
| deleted = tracker.delete_inactive_sessions() | |
| assert len(deleted) == 3 | |
| assert set(deleted) == {"session-1", "session-2", "session-3"} | |
| assert len(tracker.session_timestamp_map) == 0 | |
| def test_delete_inactive_sessions_mixed(self, mock_time, tracker): | |
| """Test when some sessions are inactive and some are active""" | |
| # Create old sessions | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("old-session-1") | |
| tracker.update_session("old-session-2") | |
| # Create recent session | |
| mock_time.return_value = 1000.0 + FOUR_HOURS + 100 | |
| tracker.update_session("recent-session") | |
| # Check for inactive sessions | |
| deleted = tracker.delete_inactive_sessions() | |
| assert len(deleted) == 2 | |
| assert set(deleted) == {"old-session-1", "old-session-2"} | |
| assert "recent-session" in tracker.session_timestamp_map | |
| assert len(tracker.session_timestamp_map) == 1 | |
| def test_delete_inactive_sessions_exactly_at_boundary(self, mock_time, tracker): | |
| """Test session exactly at FOUR_HOURS boundary""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("boundary-session") | |
| # Move time forward exactly FOUR_HOURS | |
| mock_time.return_value = 1000.0 + FOUR_HOURS | |
| deleted = tracker.delete_inactive_sessions() | |
| # Should NOT be deleted (not GREATER than FOUR_HOURS) | |
| assert deleted == [] | |
| assert "boundary-session" in tracker.session_timestamp_map | |
| def test_delete_inactive_sessions_one_second_over(self, mock_time, tracker): | |
| """Test session one second over FOUR_HOURS boundary""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session") | |
| # Move time forward FOUR_HOURS + 1 second | |
| mock_time.return_value = 1000.0 + FOUR_HOURS + 1 | |
| deleted = tracker.delete_inactive_sessions() | |
| # Should be deleted | |
| assert deleted == ["session"] | |
| assert len(tracker.session_timestamp_map) == 0 | |
| def test_delete_inactive_sessions_empty_tracker(self, mock_time, tracker): | |
| """Test deleting inactive sessions when tracker is empty""" | |
| mock_time.return_value = 1000.0 | |
| deleted = tracker.delete_inactive_sessions() | |
| assert deleted == [] | |
| assert len(tracker.session_timestamp_map) == 0 | |
| def test_delete_inactive_sessions_returns_list(self, mock_time, tracker): | |
| """Test that delete_inactive_sessions returns a list""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session") | |
| mock_time.return_value = 1000.0 + FOUR_HOURS + 100 | |
| deleted = tracker.delete_inactive_sessions() | |
| assert isinstance(deleted, list) | |
| # ==================== delete_oldest_session Tests ==================== | |
| def test_delete_oldest_session_single_session( | |
| self, mock_time, tracker, sample_session_id | |
| ): | |
| """Test deleting the oldest session when only one exists""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session(sample_session_id) | |
| oldest = tracker.delete_oldest_session() | |
| assert oldest == sample_session_id | |
| assert len(tracker.session_timestamp_map) == 0 | |
| def test_delete_oldest_session_multiple_sessions(self, mock_time, tracker): | |
| """Test deleting the oldest session among multiple""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("oldest") | |
| mock_time.return_value = 2000.0 | |
| tracker.update_session("middle") | |
| mock_time.return_value = 3000.0 | |
| tracker.update_session("newest") | |
| oldest = tracker.delete_oldest_session() | |
| assert oldest == "oldest" | |
| assert "oldest" not in tracker.session_timestamp_map | |
| assert "middle" in tracker.session_timestamp_map | |
| assert "newest" in tracker.session_timestamp_map | |
| def test_delete_oldest_session_empty_tracker(self, tracker): | |
| """Test deleting oldest session when tracker is empty""" | |
| oldest = tracker.delete_oldest_session() | |
| assert oldest is None | |
| def test_delete_oldest_session_same_timestamps(self, mock_time, tracker): | |
| """Test deleting oldest when multiple sessions have same timestamp""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session-1") | |
| tracker.update_session("session-2") | |
| tracker.update_session("session-3") | |
| oldest = tracker.delete_oldest_session() | |
| # Should delete one of them (deterministic based on dict iteration) | |
| assert oldest in ["session-1", "session-2", "session-3"] | |
| assert len(tracker.session_timestamp_map) == 2 | |
| def test_delete_oldest_session_updates_after_initial(self, mock_time, tracker): | |
| """Test that updated sessions are not considered oldest""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("first-created") | |
| mock_time.return_value = 2000.0 | |
| tracker.update_session("second-created") | |
| # Update first-created to be more recent | |
| mock_time.return_value = 3000.0 | |
| tracker.update_session("first-created") | |
| oldest = tracker.delete_oldest_session() | |
| # "second-created" should be oldest now | |
| assert oldest == "second-created" | |
| assert "first-created" in tracker.session_timestamp_map | |
| def test_delete_oldest_session_successive_calls(self, mock_time, tracker): | |
| """Test calling delete_oldest_session multiple times""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session-1") | |
| mock_time.return_value = 2000.0 | |
| tracker.update_session("session-2") | |
| mock_time.return_value = 3000.0 | |
| tracker.update_session("session-3") | |
| # Delete in order from oldest to newest | |
| first = tracker.delete_oldest_session() | |
| second = tracker.delete_oldest_session() | |
| third = tracker.delete_oldest_session() | |
| fourth = tracker.delete_oldest_session() # Empty tracker | |
| assert first == "session-1" | |
| assert second == "session-2" | |
| assert third == "session-3" | |
| assert fourth is None | |
| assert len(tracker.session_timestamp_map) == 0 | |
| def test_delete_oldest_session_does_not_affect_others(self, mock_time, tracker): | |
| """Test that deleting oldest doesn't affect other session timestamps""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("old") | |
| mock_time.return_value = 2000.0 | |
| tracker.update_session("new") | |
| new_timestamp = tracker.session_timestamp_map["new"] | |
| tracker.delete_oldest_session() | |
| # "new" should still have the same timestamp | |
| assert tracker.session_timestamp_map["new"] == new_timestamp | |
| # ==================== Integration Tests ==================== | |
| def test_full_lifecycle(self, mock_time, tracker): | |
| """Test complete session lifecycle: create, update, delete inactive, delete oldest""" | |
| # Create sessions at different times | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session-1") | |
| mock_time.return_value = 2000.0 | |
| tracker.update_session("session-2") | |
| mock_time.return_value = 3000.0 | |
| tracker.update_session("session-3") | |
| # Update session-1 to make it newer | |
| mock_time.return_value = 4000.0 | |
| tracker.update_session("session-1") | |
| # Move past FOUR_HOURS for session-2 | |
| mock_time.return_value = 2000.0 + FOUR_HOURS + 100 | |
| # Delete inactive | |
| deleted_inactive = tracker.delete_inactive_sessions() | |
| assert "session-2" in deleted_inactive | |
| assert len(tracker.session_timestamp_map) == 2 | |
| # Delete oldest | |
| oldest = tracker.delete_oldest_session() | |
| assert oldest == "session-3" # Oldest remaining | |
| assert len(tracker.session_timestamp_map) == 1 | |
| assert "session-1" in tracker.session_timestamp_map | |
| def test_update_then_delete_same_session(self, tracker, sample_session_id): | |
| """Test updating then immediately deleting the same session""" | |
| tracker.update_session(sample_session_id) | |
| assert sample_session_id in tracker.session_timestamp_map | |
| tracker.delete_session(sample_session_id) | |
| assert sample_session_id not in tracker.session_timestamp_map | |
| def test_delete_oldest_after_delete_inactive(self, mock_time, tracker): | |
| """Test delete_oldest after delete_inactive has removed some sessions""" | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("old-1") | |
| tracker.update_session("old-2") | |
| mock_time.return_value = 1000.0 + FOUR_HOURS + 100 | |
| tracker.update_session("new-1") | |
| tracker.update_session("new-2") | |
| # Delete inactive (removes old-1 and old-2) | |
| tracker.delete_inactive_sessions() | |
| # Delete oldest of remaining | |
| oldest = tracker.delete_oldest_session() | |
| # Both new sessions have same timestamp, one should be deleted | |
| assert oldest in ["new-1", "new-2"] | |
| assert len(tracker.session_timestamp_map) == 1 | |
| def test_stress_test_many_sessions(self, mock_time, tracker): | |
| """Test handling many sessions""" | |
| num_sessions = 1000 | |
| for i in range(num_sessions): | |
| mock_time.return_value = 1000.0 + i | |
| tracker.update_session(f"session-{i}") | |
| assert len(tracker.session_timestamp_map) == num_sessions | |
| # Delete oldest should remove session-0 | |
| oldest = tracker.delete_oldest_session() | |
| assert oldest == "session-0" | |
| assert len(tracker.session_timestamp_map) == num_sessions - 1 | |
| def test_timestamp_precision(self, mock_time, tracker): | |
| """Test that timestamps maintain precision""" | |
| mock_time.return_value = 1234567890.123456789 | |
| tracker.update_session("session") | |
| stored_timestamp = tracker.session_timestamp_map["session"] | |
| assert stored_timestamp == 1234567890.123456789 | |
| def test_empty_session_id(self, tracker): | |
| """Test handling empty string as session ID""" | |
| tracker.update_session("") | |
| assert "" in tracker.session_timestamp_map | |
| tracker.delete_session("") | |
| assert "" not in tracker.session_timestamp_map | |
| def test_delete_inactive_preserves_order_independence(self, mock_time, tracker): | |
| """Test that delete_inactive doesn't depend on insertion order""" | |
| # Create sessions in random order | |
| mock_time.return_value = 3000.0 | |
| tracker.update_session("session-3") | |
| mock_time.return_value = 1000.0 | |
| tracker.update_session("session-1") | |
| mock_time.return_value = 2000.0 | |
| tracker.update_session("session-2") | |
| # All should be deleted | |
| mock_time.return_value = 3000.0 + FOUR_HOURS + 100 | |
| deleted = tracker.delete_inactive_sessions() | |
| assert len(deleted) == 3 | |
| assert set(deleted) == {"session-1", "session-2", "session-3"} | |