File size: 13,275 Bytes
5f613ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Tests for the gamification system in SkillSprout
"""
import pytest
from unittest.mock import Mock, patch
import sys
import os

# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from space_app import (
    GamificationManager, Achievement, UserStats, EnhancedUserProgress
)

class TestAchievement:
    """Test cases for Achievement data class"""
    
    def test_achievement_creation(self):
        """Test creating an achievement"""
        achievement = Achievement(
            id="test_achievement",
            name="Test Achievement",
            description="A test achievement",
            icon="๐Ÿ†",
            unlocked=False,
            unlock_condition="Complete 1 lesson"
        )
        
        assert achievement.id == "test_achievement"
        assert achievement.name == "Test Achievement"
        assert achievement.unlocked is False
        assert achievement.icon == "๐Ÿ†"
    
    def test_achievement_defaults(self):
        """Test achievement with default values"""
        achievement = Achievement(
            id="simple",
            name="Simple",
            description="Simple achievement",
            icon="โญ"
        )
        
        assert achievement.unlocked is False
        assert achievement.unlock_condition == ""

class TestUserStats:
    """Test cases for UserStats class"""
    
    def test_user_stats_creation(self):
        """Test creating user stats"""
        stats = UserStats(user_id="test_user")
        
        assert stats.user_id == "test_user"
        assert stats.total_points == 0
        assert stats.level == 1
        assert stats.achievements == []
        assert stats.streak_days == 0
        assert stats.total_lessons == 0
        assert stats.total_quizzes == 0
        assert stats.correct_answers == 0
    
    def test_add_points_no_level_up(self):
        """Test adding points without level up"""
        stats = UserStats(user_id="test_user")
        
        # Act
        stats.add_points(50)
        
        # Assert
        assert stats.total_points == 50
        assert stats.level == 1  # Should still be level 1
    
    def test_add_points_level_up(self):
        """Test adding points that triggers level up"""
        stats = UserStats(user_id="test_user")
        
        # Act
        stats.add_points(150)  # Should trigger level up
        
        # Assert
        assert stats.total_points == 150
        assert stats.level == 2
    
    def test_add_points_multiple_levels(self):
        """Test adding points that triggers multiple level ups"""
        stats = UserStats(user_id="test_user")
        
        # Act
        stats.add_points(450)  # Should reach level 5
        
        # Assert
        assert stats.total_points == 450
        assert stats.level == 5
    
    def test_add_points_max_level(self):
        """Test that level doesn't exceed maximum"""
        stats = UserStats(user_id="test_user")
        
        # Act
        stats.add_points(2000)  # Way more than needed for max level
        
        # Assert
        assert stats.level == 10  # Should cap at level 10
    
    def test_get_accuracy_no_quizzes(self):
        """Test accuracy calculation with no quizzes"""
        stats = UserStats(user_id="test_user")
        
        # Act & Assert
        assert stats.get_accuracy() == 0.0
    
    def test_get_accuracy_with_quizzes(self):
        """Test accuracy calculation with quiz data"""
        stats = UserStats(user_id="test_user")
        stats.total_quizzes = 10
        stats.correct_answers = 8
        
        # Act
        accuracy = stats.get_accuracy()
        
        # Assert
        assert accuracy == 80.0

class TestEnhancedUserProgress:
    """Test cases for EnhancedUserProgress class"""
    
    def test_enhanced_progress_creation(self):
        """Test creating enhanced user progress"""
        progress = EnhancedUserProgress(
            user_id="test_user",
            skill="Python Programming"
        )
        
        assert progress.user_id == "test_user"
        assert progress.skill == "Python Programming"
        assert progress.lessons_completed == 0
        assert progress.quiz_scores == []
        assert progress.time_spent == []
        assert progress.mastery_level == 0.0

class TestGamificationManager:
    """Test cases for GamificationManager class"""
    
    @pytest.fixture
    def gamification_manager(self):
        """Create a GamificationManager instance for testing"""
        return GamificationManager()
    
    def test_gamification_manager_initialization(self, gamification_manager):
        """Test GamificationManager initialization"""
        assert len(gamification_manager.user_stats) == 0
        assert len(gamification_manager.achievements) > 0
        
        # Check that all required achievements exist
        required_achievements = [
            "first_steps", "quiz_master", "persistent", "scholar",
            "expert", "polyglot", "perfectionist", "speed", 
            "consistent", "explorer"
        ]
        
        for achievement_id in required_achievements:
            assert achievement_id in gamification_manager.achievements
    
    def test_get_user_stats_new_user(self, gamification_manager):
        """Test getting stats for a new user"""
        # Act
        stats = gamification_manager.get_user_stats("new_user")
        
        # Assert
        assert isinstance(stats, UserStats)
        assert stats.user_id == "new_user"
        assert stats.total_points == 0
        assert stats.level == 1
    
    def test_get_user_stats_existing_user(self, gamification_manager):
        """Test getting stats for existing user"""
        # Arrange
        user_id = "existing_user"
        stats1 = gamification_manager.get_user_stats(user_id)
        stats1.total_points = 100
        
        # Act
        stats2 = gamification_manager.get_user_stats(user_id)
        
        # Assert
        assert stats2.total_points == 100
        assert stats1 is stats2  # Should be same object
    
    def test_check_achievements_first_steps(self, gamification_manager):
        """Test unlocking first steps achievement"""
        # Arrange
        user_id = "test_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        
        # Set up conditions for first steps achievement
        stats = gamification_manager.get_user_stats(user_id)
        stats.total_lessons = 1
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        assert len(newly_unlocked) > 0
        achievement_ids = [a.id for a in newly_unlocked]
        assert "first_steps" in achievement_ids
        assert "first_steps" in stats.achievements
    
    def test_check_achievements_quiz_master(self, gamification_manager):
        """Test unlocking quiz master achievement"""
        # Arrange
        user_id = "quiz_master_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        progress.quiz_scores = [100, 80, 100]  # Has perfect score
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        achievement_ids = [a.id for a in newly_unlocked]
        assert "quiz_master" in achievement_ids
    
    def test_check_achievements_persistent(self, gamification_manager):
        """Test unlocking persistent learner achievement"""
        # Arrange
        user_id = "persistent_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        
        stats = gamification_manager.get_user_stats(user_id)
        stats.total_lessons = 5
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        achievement_ids = [a.id for a in newly_unlocked]
        assert "persistent" in achievement_ids
    
    def test_check_achievements_no_new_unlocks(self, gamification_manager):
        """Test checking achievements when none should be unlocked"""
        # Arrange
        user_id = "minimal_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        # User has minimal progress, shouldn't unlock anything
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        assert len(newly_unlocked) == 0
    
    def test_check_achievements_already_unlocked(self, gamification_manager):
        """Test that already unlocked achievements aren't returned again"""
        # Arrange
        user_id = "repeat_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        
        stats = gamification_manager.get_user_stats(user_id)
        stats.total_lessons = 1
        stats.achievements = ["first_steps"]  # Already unlocked
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        achievement_ids = [a.id for a in newly_unlocked]
        assert "first_steps" not in achievement_ids
    
    def test_check_achievements_points_awarded(self, gamification_manager):
        """Test that bonus points are awarded for achievements"""
        # Arrange
        user_id = "points_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        
        stats = gamification_manager.get_user_stats(user_id)
        initial_points = stats.total_points
        stats.total_lessons = 1  # Will unlock first_steps
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        assert len(newly_unlocked) > 0
        assert stats.total_points > initial_points  # Should have bonus points
    
    def test_achievement_perfectionist(self, gamification_manager):
        """Test perfectionist achievement (5 perfect scores)"""
        # Arrange
        user_id = "perfectionist_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        progress.quiz_scores = [100, 100, 100, 100, 100, 90]  # 5 perfect scores
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        achievement_ids = [a.id for a in newly_unlocked]
        assert "perfectionist" in achievement_ids
    
    def test_achievement_consistent(self, gamification_manager):
        """Test consistent achievement (7-day streak)"""
        # Arrange
        user_id = "consistent_user"
        progress = EnhancedUserProgress(user_id=user_id, skill="Python")
        
        stats = gamification_manager.get_user_stats(user_id)
        stats.streak_days = 7
        
        # Act
        newly_unlocked = gamification_manager.check_achievements(user_id, progress)
        
        # Assert
        achievement_ids = [a.id for a in newly_unlocked]
        assert "consistent" in achievement_ids

@pytest.mark.integration
class TestGamificationIntegration:
    """Integration tests for gamification with other systems"""
    
    def test_gamification_with_lesson_completion(self):
        """Test gamification integration when completing lessons"""
        # This would test the integration between the main app and gamification
        # Requires importing the enhanced app components
        pass
    
    def test_gamification_with_quiz_submission(self):
        """Test gamification integration when submitting quizzes"""
        pass

@pytest.mark.unit
class TestAchievementConditions:
    """Test specific achievement unlock conditions"""
    
    @pytest.fixture
    def sample_progress(self):
        """Create sample progress data for testing"""        
        return EnhancedUserProgress(
            user_id="test_user",
            skill="Python Programming",
            lessons_completed=0,
            quiz_scores=[],
            time_spent=[],
            mastery_level=0.0
        )
    
    def test_scholar_achievement_condition(self, gamification_manager, enhanced_user_progress):
        """Test scholar achievement (10 lessons)"""
        # Arrange
        stats = gamification_manager.get_user_stats("test_user")
        stats.total_lessons = 10
        
        # Act
        newly_unlocked = gamification_manager.check_achievements("test_user", enhanced_user_progress)
        
        # Assert
        achievement_ids = [a.id for a in newly_unlocked]
        assert "scholar" in achievement_ids
    
    def test_expert_achievement_condition(self, gamification_manager, enhanced_user_progress):
        """Test expert achievement (20 lessons)"""
        # Arrange
        stats = gamification_manager.get_user_stats("test_user")
        stats.total_lessons = 20
        
        # Act
        newly_unlocked = gamification_manager.check_achievements("test_user", enhanced_user_progress)
        
        # Assert
        achievement_ids = [a.id for a in newly_unlocked]
        assert "expert" in achievement_ids
        assert "scholar" in achievement_ids  # Should also unlock scholar
        assert "persistent" in achievement_ids  # Should also unlock persistent