Initial commit
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from accounts.access import has_lifetime_premium
|
||||
from accounts.models import DailyMissionClaim, PremiumEntitlement, StudentProfile
|
||||
from exams.models import ExamSet, Question, Section, StudentAnswer, StudentAttempt
|
||||
|
||||
|
||||
class StudentDashboardTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="anvar", password="test-password"
|
||||
)
|
||||
|
||||
def test_dashboard_requires_login(self):
|
||||
response = self.client.get(reverse("student_dashboard"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_dashboard_recovers_missing_profile_and_has_working_links(self):
|
||||
self.user.studentprofile.delete()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get(reverse("student_dashboard"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "No completed tests yet")
|
||||
self.assertContains(response, reverse("exam_list"))
|
||||
self.assertNotContains(response, 'href="#"')
|
||||
self.assertTrue(hasattr(self.user, "studentprofile"))
|
||||
|
||||
def test_dashboard_displays_real_attempt_metrics(self):
|
||||
exam = ExamSet.objects.create(title="IELTS Academic Mock 1")
|
||||
section = Section.objects.create(
|
||||
exam_set=exam,
|
||||
order=1,
|
||||
section_type="reading",
|
||||
time_limit_minutes=60,
|
||||
)
|
||||
correct_question = Question.objects.create(
|
||||
section=section,
|
||||
order=1,
|
||||
question_type="mcq",
|
||||
prompt="Correct",
|
||||
correct_answer="A",
|
||||
)
|
||||
wrong_question = Question.objects.create(
|
||||
section=section,
|
||||
order=2,
|
||||
question_type="mcq",
|
||||
prompt="Wrong",
|
||||
correct_answer="B",
|
||||
)
|
||||
attempt = StudentAttempt.objects.create(
|
||||
student=self.user,
|
||||
exam_set=exam,
|
||||
is_complete=True,
|
||||
submitted_at=timezone.now(),
|
||||
)
|
||||
StudentAttempt.objects.filter(pk=attempt.pk).update(
|
||||
started_at=timezone.now() - timedelta(minutes=30)
|
||||
)
|
||||
StudentAnswer.objects.create(
|
||||
attempt=attempt,
|
||||
question=correct_question,
|
||||
answer_text="A",
|
||||
is_correct=True,
|
||||
)
|
||||
StudentAnswer.objects.create(
|
||||
attempt=attempt,
|
||||
question=wrong_question,
|
||||
answer_text="A",
|
||||
is_correct=False,
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get(reverse("student_dashboard"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "IELTS Academic Mock 1")
|
||||
self.assertEqual(response.context["tests_taken"], 1)
|
||||
self.assertEqual(response.context["accuracy"], 50)
|
||||
self.assertEqual(response.context["skills"][0]["value"], 50)
|
||||
self.assertIn(response.context["avg_time"], {"29m", "30m"})
|
||||
self.assertContains(response, reverse("results", args=[attempt.pk]))
|
||||
|
||||
def test_dashboard_uses_manual_band_scores_for_writing_progress(self):
|
||||
exam = ExamSet.objects.create(title="Reviewed Writing")
|
||||
section = Section.objects.create(
|
||||
exam_set=exam,
|
||||
order=1,
|
||||
section_type="writing",
|
||||
time_limit_minutes=40,
|
||||
)
|
||||
question = Question.objects.create(
|
||||
section=section,
|
||||
order=1,
|
||||
question_type="essay",
|
||||
prompt="Write an essay.",
|
||||
)
|
||||
attempt = StudentAttempt.objects.create(
|
||||
student=self.user,
|
||||
exam_set=exam,
|
||||
is_complete=True,
|
||||
submitted_at=timezone.now(),
|
||||
)
|
||||
StudentAnswer.objects.create(
|
||||
attempt=attempt,
|
||||
question=question,
|
||||
answer_text="Response",
|
||||
manual_score=7.2,
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get(reverse("student_dashboard"))
|
||||
|
||||
writing = next(skill for skill in response.context["skills"] if skill["name"] == "Writing")
|
||||
self.assertEqual(writing["value"], 80)
|
||||
|
||||
def test_completed_daily_mission_can_be_claimed_once_for_xp(self):
|
||||
exam = ExamSet.objects.create(title="Daily mission test")
|
||||
section = Section.objects.create(
|
||||
exam_set=exam, order=1, section_type="reading", time_limit_minutes=20
|
||||
)
|
||||
question = Question.objects.create(
|
||||
section=section,
|
||||
order=1,
|
||||
question_type="gap",
|
||||
prompt="Answer",
|
||||
correct_answer="answer",
|
||||
)
|
||||
attempt = StudentAttempt.objects.create(
|
||||
student=self.user,
|
||||
exam_set=exam,
|
||||
is_complete=True,
|
||||
submitted_at=timezone.now(),
|
||||
)
|
||||
StudentAnswer.objects.create(
|
||||
attempt=attempt, question=question, answer_text="answer", is_correct=True
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.post(reverse("claim_daily_mission", args=["complete_test"]))
|
||||
self.user.studentprofile.refresh_from_db()
|
||||
|
||||
self.assertRedirects(response, reverse("student_dashboard"))
|
||||
self.assertEqual(self.user.studentprofile.xp, 30)
|
||||
self.assertTrue(
|
||||
DailyMissionClaim.objects.filter(
|
||||
user=self.user, mission_key="complete_test"
|
||||
).exists()
|
||||
)
|
||||
|
||||
self.client.post(reverse("claim_daily_mission", args=["complete_test"]))
|
||||
self.user.studentprofile.refresh_from_db()
|
||||
self.assertEqual(self.user.studentprofile.xp, 30)
|
||||
|
||||
|
||||
class StudentSettingsTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="student", email="old@example.com", password="test-password"
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_settings_page_requires_login(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(reverse("student_settings"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_student_can_update_profile_and_goals(self):
|
||||
response = self.client.post(
|
||||
reverse("student_settings"),
|
||||
{
|
||||
"first_name": "Anvar",
|
||||
"last_name": "Student",
|
||||
"target_band": "8.0",
|
||||
"daily_goal": "90",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("student_settings"))
|
||||
self.user.refresh_from_db()
|
||||
self.user.studentprofile.refresh_from_db()
|
||||
self.assertEqual(self.user.first_name, "Anvar")
|
||||
self.assertEqual(self.user.email, "old@example.com")
|
||||
self.assertEqual(self.user.studentprofile.target_band, 8.0)
|
||||
self.assertEqual(self.user.studentprofile.daily_goal, 90)
|
||||
self.assertContains(response, "Your profile settings have been updated.")
|
||||
|
||||
def test_settings_reject_invalid_goals(self):
|
||||
response = self.client.post(
|
||||
reverse("student_settings"),
|
||||
{
|
||||
"target_band": "10.0",
|
||||
"daily_goal": "5",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFormError(response.context["form"], "target_band", "Ensure this value is less than or equal to 9.")
|
||||
self.assertFormError(response.context["form"], "daily_goal", "Ensure this value is greater than or equal to 10.")
|
||||
|
||||
def test_settings_reject_target_bands_outside_half_band_increments(self):
|
||||
response = self.client.post(
|
||||
reverse("student_settings"),
|
||||
{"target_band": "7.3", "daily_goal": "60"},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFormError(
|
||||
response.context["form"],
|
||||
"target_band",
|
||||
"Choose an IELTS band score in 0.5 increments.",
|
||||
)
|
||||
|
||||
|
||||
class LifetimePremiumTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="premium-student",
|
||||
email="premium@example.com",
|
||||
password="test-password",
|
||||
)
|
||||
|
||||
def test_premium_page_explains_one_time_access(self):
|
||||
response = self.client.get(reverse("premium"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "One payment.")
|
||||
self.assertContains(response, "No subscription")
|
||||
|
||||
def test_active_and_revoked_entitlements_are_distinguished(self):
|
||||
entitlement = PremiumEntitlement.objects.create(
|
||||
user=self.user,
|
||||
source=PremiumEntitlement.SOURCE_PAYMENT,
|
||||
order_reference="ORDER-1001",
|
||||
)
|
||||
self.assertTrue(has_lifetime_premium(self.user))
|
||||
entitlement.revoked_at = timezone.now()
|
||||
entitlement.save(update_fields=["revoked_at"])
|
||||
self.assertFalse(has_lifetime_premium(self.user))
|
||||
|
||||
def test_staff_has_premium_access_without_entitlement(self):
|
||||
self.user.is_staff = True
|
||||
self.user.save(update_fields=["is_staff"])
|
||||
self.assertTrue(has_lifetime_premium(self.user))
|
||||
|
||||
def test_management_command_grants_and_restores_access(self):
|
||||
call_command(
|
||||
"grant_lifetime_premium",
|
||||
self.user.email,
|
||||
source="payment",
|
||||
order_reference="ORDER-2002",
|
||||
)
|
||||
entitlement = PremiumEntitlement.objects.get(user=self.user)
|
||||
self.assertTrue(entitlement.is_active)
|
||||
self.assertEqual(entitlement.order_reference, "ORDER-2002")
|
||||
|
||||
entitlement.revoked_at = timezone.now()
|
||||
entitlement.save(update_fields=["revoked_at"])
|
||||
call_command("grant_lifetime_premium", self.user.username)
|
||||
entitlement.refresh_from_db()
|
||||
self.assertTrue(entitlement.is_active)
|
||||
|
||||
|
||||
class LeaderboardTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="ranked-student", password="test-password"
|
||||
)
|
||||
self.profile, _ = StudentProfile.objects.get_or_create(user=self.user)
|
||||
|
||||
def test_landing_page_uses_demo_leaderboard_preview(self):
|
||||
response = self.client.get(reverse("home"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Weekly leaderboard")
|
||||
self.assertContains(response, "Alex")
|
||||
self.assertContains(response, "Join the leaderboard")
|
||||
|
||||
def test_leaderboard_requires_login(self):
|
||||
response = self.client.get(reverse("leaderboard"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_completed_scored_attempt_places_student_in_rankings(self):
|
||||
exam = ExamSet.objects.create(title="Ranked Reading")
|
||||
section = Section.objects.create(
|
||||
exam_set=exam,
|
||||
order=1,
|
||||
section_type="reading",
|
||||
time_limit_minutes=60,
|
||||
)
|
||||
question = Question.objects.create(
|
||||
section=section,
|
||||
order=1,
|
||||
question_type="gap",
|
||||
prompt="Answer",
|
||||
correct_answer="valid",
|
||||
)
|
||||
attempt = StudentAttempt.objects.create(
|
||||
student=self.user,
|
||||
exam_set=exam,
|
||||
is_complete=True,
|
||||
submitted_at=timezone.now(),
|
||||
)
|
||||
StudentAnswer.objects.create(
|
||||
attempt=attempt,
|
||||
question=question,
|
||||
answer_text="valid",
|
||||
is_correct=True,
|
||||
)
|
||||
self.profile.display_name = "Akhmed"
|
||||
self.profile.country = "Uzbekistan"
|
||||
self.profile.save(update_fields=["display_name", "country"])
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get(reverse("leaderboard"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Akhmed")
|
||||
self.assertContains(response, "Uzbekistan")
|
||||
self.assertContains(response, "(You)")
|
||||
self.assertEqual(response.context["current_row"]["band"], 9.0)
|
||||
|
||||
def test_settings_save_public_leaderboard_identity(self):
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.post(
|
||||
reverse("student_settings"),
|
||||
{
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"display_name": "StudyFox",
|
||||
"country": "Uzbekistan",
|
||||
"target_band": "7.5",
|
||||
"daily_goal": "60",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("student_settings"))
|
||||
self.profile.refresh_from_db()
|
||||
self.assertEqual(self.profile.display_name, "StudyFox")
|
||||
self.assertEqual(self.profile.country, "Uzbekistan")
|
||||
Reference in New Issue
Block a user