Bin
2025-12-17 262fecaa75b2909ad244f12c3b079ed3ff4ae329
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
"""
Tests for Redis user activity caching functionality.
"""
 
from datetime import timedelta
from unittest.mock import MagicMock, patch
 
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone
from users.functions.last_activity import (
    SYNC_THRESHOLD,
    USER_ACTIVITY_COUNTER_KEY,
    _bulk_update_user_activities,
    _get_user_activity_key,
    cleanup_redis_activity_data,
    clear_batch_user_ids,
    get_activity_counter,
    get_batch_user_ids,
    get_user_last_activity,
    increment_activity_counter,
    reset_activity_counter,
    set_user_last_activity,
    should_sync_activities,
)
 
User = get_user_model()
 
 
class TestRedisActivity(TestCase):
    """Test Redis activity functions."""
 
    def setUp(self):
        self.user = User.objects.create_user(email='test@example.com', username='testuser', password='testpass123')
        self.test_time = timezone.now()
 
    def tearDown(self):
        # Clean up Redis data after each test
        with patch('users.functions.last_activity.redis_connected', return_value=True):
            cleanup_redis_activity_data({self.user.id})
            reset_activity_counter()
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_set_user_last_activity_success(self, mock_get_connection, mock_redis_connected):
        """Test successful setting of user activity."""
        mock_redis_client = MagicMock()
        mock_get_connection.return_value = mock_redis_client
        mock_redis_client.incr.return_value = 1
 
        result = set_user_last_activity(self.user.id, self.test_time)
 
        self.assertTrue(result)
        mock_redis_client.setex.assert_called_once()
        mock_redis_client.sadd.assert_called_once()
        self.assertEqual(mock_redis_client.expire.call_count, 2)  # One for batch key, one for counter
        mock_redis_client.incr.assert_called_once()
 
    @patch('users.functions.last_activity.redis_connected', return_value=False)
    def test_set_user_last_activity_redis_disconnected(self, mock_redis_connected):
        """Test setting activity when Redis is disconnected."""
        result = set_user_last_activity(self.user.id, self.test_time)
        self.assertFalse(result)
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_get_user_last_activity_from_redis(self, mock_get_connection, mock_redis_connected):
        """Test getting user activity from Redis."""
        with patch('users.functions.last_activity._redis', MagicMock()):
            mock_redis_client = MagicMock()
            mock_get_connection.return_value = mock_redis_client
            mock_redis_client.get.return_value = self.test_time.isoformat().encode('utf-8')
 
            result = get_user_last_activity(self.user.id)
 
            self.assertEqual(result, self.test_time)
            mock_redis_client.get.assert_called_once()
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_get_user_last_activity_no_redis_data(self, mock_get_connection, mock_redis_connected):
        """Test getting activity when Redis has no data (returns None)."""
        with patch('users.functions.last_activity._redis', MagicMock()):
            mock_redis_client = MagicMock()
            mock_get_connection.return_value = mock_redis_client
            mock_redis_client.get.return_value = None
 
            result = get_user_last_activity(self.user.id)
 
            self.assertIsNone(result)
            mock_redis_client.get.assert_called_once()
 
    @patch('users.functions.last_activity.redis_connected', return_value=False)
    def test_get_user_last_activity_redis_disconnected(self, mock_redis_connected):
        """Test getting activity when Redis is disconnected (returns None)."""
        with patch('users.functions.last_activity._redis', None):
            result = get_user_last_activity(self.user.id)
 
            self.assertIsNone(result)
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_increment_activity_counter(self, mock_get_connection, mock_redis_connected):
        """Test incrementing activity counter."""
        mock_redis_client = MagicMock()
        mock_get_connection.return_value = mock_redis_client
        mock_redis_client.incr.return_value = 5
 
        result = increment_activity_counter()
 
        self.assertEqual(result, 5)
        mock_redis_client.incr.assert_called_with(USER_ACTIVITY_COUNTER_KEY)
        mock_redis_client.expire.assert_called_once()
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_get_activity_counter(self, mock_get_connection, mock_redis_connected):
        """Test getting activity counter."""
        mock_redis_client = MagicMock()
        mock_get_connection.return_value = mock_redis_client
        mock_redis_client.get.return_value = b'10'
 
        result = get_activity_counter()
 
        self.assertEqual(result, 10)
        mock_redis_client.get.assert_called_once()
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_reset_activity_counter(self, mock_get_connection, mock_redis_connected):
        """Test resetting activity counter."""
        mock_redis_client = MagicMock()
        mock_get_connection.return_value = mock_redis_client
 
        result = reset_activity_counter()
 
        self.assertTrue(result)
        mock_redis_client.set.assert_called_once_with(USER_ACTIVITY_COUNTER_KEY, 0)
        mock_redis_client.expire.assert_called_once()
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_get_batch_user_ids(self, mock_get_connection, mock_redis_connected):
        """Test getting batch user IDs."""
        mock_redis_client = MagicMock()
        mock_get_connection.return_value = mock_redis_client
        mock_redis_client.smembers.return_value = {b'1', b'2', b'3'}
 
        result = get_batch_user_ids()
 
        self.assertEqual(result, {1, 2, 3})
        mock_redis_client.smembers.assert_called_once()
 
    @patch('users.functions.last_activity.redis_connected', return_value=True)
    @patch('users.functions.last_activity.get_connection')
    def test_clear_batch_user_ids(self, mock_get_connection, mock_redis_connected):
        """Test clearing batch user IDs."""
        mock_redis_client = MagicMock()
        mock_get_connection.return_value = mock_redis_client
 
        result = clear_batch_user_ids({1, 2, 3})
 
        self.assertTrue(result)
        mock_redis_client.srem.assert_called_once()
 
    @patch('users.functions.last_activity.get_activity_counter')
    def test_should_sync_activities(self, mock_get_counter):
        """Test sync threshold check."""
        # Below threshold
        mock_get_counter.return_value = SYNC_THRESHOLD - 1
        self.assertFalse(should_sync_activities())
 
        # At threshold
        mock_get_counter.return_value = SYNC_THRESHOLD
        self.assertTrue(should_sync_activities())
 
        # Above threshold
        mock_get_counter.return_value = SYNC_THRESHOLD + 1
        self.assertTrue(should_sync_activities())
 
    def test_get_user_activity_key(self):
        """Test Redis key generation."""
        expected_key = f'user_activity:{self.user.id}'
        result = _get_user_activity_key(self.user.id)
        self.assertEqual(result, expected_key)
 
 
class TestUserLastActivityMixin(TestCase):
    """Test UserLastActivityMixin methods."""
 
    def setUp(self):
        self.user = User.objects.create_user(email='test@example.com', username='testuser', password='testpass123')
 
    @patch('users.models.set_user_last_activity')
    @patch('users.models.schedule_activity_sync')
    def test_update_last_activity_redis_success(self, mock_schedule, mock_set_activity):
        """Test updating last activity with Redis success."""
        mock_set_activity.return_value = True
 
        self.user.update_last_activity()
 
        mock_set_activity.assert_called_once()
        mock_schedule.assert_called_once()
 
    @patch('users.models.set_user_last_activity')
    def test_update_last_activity_redis_failure(self, mock_set_activity):
        """Test updating last activity with Redis failure (fallback to DB)."""
        mock_set_activity.return_value = False
        original_activity = self.user.last_activity
 
        self.user.update_last_activity()
 
        mock_set_activity.assert_called_once()
        # Check that database was updated
        self.user.refresh_from_db()
        self.assertNotEqual(self.user.last_activity, original_activity)
 
    @patch('users.models.get_user_last_activity')
    def test_get_last_activity_cached(self, mock_get_activity):
        """Test getting cached last activity."""
        test_time = timezone.now()
        mock_get_activity.return_value = test_time
 
        result = self.user.get_last_activity()
 
        self.assertEqual(result, test_time)
        mock_get_activity.assert_called_once_with(self.user.id)
 
    @patch('users.models.get_user_last_activity')
    def test_last_activity_cached_property(self, mock_get_activity):
        """Test last_activity_cached property."""
        test_time = timezone.now()
        mock_get_activity.return_value = test_time
 
        result = self.user.last_activity_cached
 
        self.assertEqual(result, test_time)
        mock_get_activity.assert_called_once_with(self.user.id)
 
 
class TestSyncTasks(TestCase):
    """Test synchronization tasks."""
 
    def setUp(self):
        self.user = User.objects.create_user(email='test@example.com', username='testuser', password='testpass123')
 
    def test_bulk_update_user_activities_success(self):
        """Test bulk updating user activities."""
        test_time = timezone.now()
        activities = [{'user_id': self.user.id, 'last_activity': test_time}]
 
        result = _bulk_update_user_activities(activities)
 
        self.assertTrue(result['success'])
        self.assertEqual(result['processed'], 1)
        self.assertEqual(result['errors'], 0)
 
        # Check database was updated
        self.user.refresh_from_db()
        self.assertEqual(self.user.last_activity, test_time)
 
    def test_bulk_update_user_activities_nonexistent_user(self):
        """Test bulk update with non-existent user."""
        test_time = timezone.now()
        activities = [{'user_id': 99999, 'last_activity': test_time}]
 
        result = _bulk_update_user_activities(activities)
 
        self.assertTrue(result['success'])
        self.assertEqual(result['processed'], 0)
        self.assertEqual(result['errors'], 1)
 
    def test_bulk_update_user_activities_outdated_activity(self):
        """Test bulk update with outdated activity."""
        # Set user's current activity to a future time
        future_time = timezone.now() + timedelta(hours=1)
        self.user.last_activity = future_time
        self.user.save()
 
        # Try to update with older time
        past_time = timezone.now() - timedelta(hours=1)
        activities = [{'user_id': self.user.id, 'last_activity': past_time}]
 
        result = _bulk_update_user_activities(activities)
 
        self.assertTrue(result['success'])
        self.assertEqual(result['processed'], 1)
        self.assertEqual(result['errors'], 0)
        self.assertEqual(result['updated'], 0)  # No actual update performed
 
        # Check database was not updated
        self.user.refresh_from_db()
        self.assertEqual(self.user.last_activity, future_time)
 
    def test_bulk_update_user_activities_empty_list(self):
        """Test bulk update with empty activities list."""
        result = _bulk_update_user_activities([])
 
        self.assertTrue(result['success'])
        self.assertEqual(result['processed'], 0)
        self.assertEqual(result['errors'], 0)