Spaces:
Paused
Paused
File size: 16,762 Bytes
8b9e569 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 | 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"""
@pytest.fixture
def tracker(self):
"""Fresh tracker instance for each test"""
return SessionTracker()
@pytest.fixture
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
@patch("time.time")
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 ====================
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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 ====================
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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 ====================
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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
@patch("time.time")
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"}
|