You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

273 lines
11 KiB

import tempfile
from unittest import mock
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings
from django.urls import reverse
from django.utils import timezone
from dj_language.models import Language
from rest_framework import status
from rest_framework.test import APITestCase
from apps.account.models import ProfessorUser, StudentUser
from apps.course.models import (
Course,
CourseCategory,
CourseLiveSession,
Participant,
)
from apps.course.views.course import get_course_slug_value
@override_settings(
PLUGNMEET_SERVER_URL='https://meet.example.com',
PLUGNMEET_API_KEY='test-key',
PLUGNMEET_API_SECRET='test-secret',
MEDIA_ROOT=tempfile.gettempdir(),
ONLINE_CLASS_FRONTEND_DOMAIN='http://testserver',
)
class CourseLiveSessionAPITests(APITestCase):
def setUp(self):
Language.objects.update_or_create(
id=69,
defaults={
'code': 'en',
'name': 'English',
'status': True,
'countries': [],
},
)
self.professor = ProfessorUser.objects.create(
email='prof@example.com',
fullname='Professor Sample',
experience_years=5,
)
self.student = StudentUser.objects.create(
email='student@example.com',
fullname='Student Sample',
)
self.category = CourseCategory.objects.create(name='Category', slug='category')
thumbnail = SimpleUploadedFile('thumb.jpg', b'filecontent', content_type='image/jpeg')
self.course = Course.objects.create(
title='Sample Course',
slug='sample-course',
category=self.category,
professor=self.professor,
thumbnail=thumbnail,
video_type=Course.VedioTypeChoices.YOUTUBE_LINK,
video_link='https://example.com/video',
is_online=True,
online_link='https://example.com/live',
level=Course.LevelChoices.BEGINNER,
duration=10,
lessons_count=2,
description='Description',
short_description='Short',
status=Course.StatusChoices.ONGOING,
is_free=True,
)
professor_avatar = SimpleUploadedFile('prof-avatar.jpg', b'avatar', content_type='image/jpeg')
self.professor.avatar = professor_avatar
self.professor.save(update_fields=['avatar'])
student_avatar = SimpleUploadedFile('student-avatar.jpg', b'avatar', content_type='image/jpeg')
self.student.avatar = student_avatar
self.student.save(update_fields=['avatar'])
@mock.patch('apps.course.views.live_session.PlugNMeetClient')
def test_professor_can_create_room(self, mock_client_cls):
mock_client = mock_client_cls.return_value
mock_client.create_room.return_value = {'status': 'success'}
self.client.force_authenticate(user=self.professor)
url = reverse('course-live-session-room-create', kwargs={'slug': self.course.slug})
payload = {
'room_id': 'custom-room-id',
'subject': 'Algebra Session',
}
response = self.client.post(url, payload, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
mock_client.create_room.assert_called_once()
self.assertTrue(
CourseLiveSession.objects.filter(course=self.course, room_id='custom-room-id').exists()
)
@mock.patch('apps.course.views.live_session.PlugNMeetClient')
def test_professor_receives_admin_token(self, mock_client_cls):
mock_client = mock_client_cls.return_value
mock_client.get_join_token.return_value = {'token': 'abc123'}
session = CourseLiveSession.objects.create(
course=self.course,
subject='Session',
started_at=timezone.now(),
room_id='room-123',
)
self.client.force_authenticate(user=self.professor)
url = reverse('course-live-session-token')
response = self.client.post(url, {'room_id': session.room_id}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
args, _ = mock_client.get_join_token.call_args
payload = args[0]
self.assertTrue(payload['user_info']['is_admin'])
profile_pic = payload['user_info']['user_metadata'].get('profilePic')
self.assertEqual(profile_pic, f"http://testserver{self.professor.avatar.url}")
self.assertEqual(response.data['token'], 'abc123')
@mock.patch('apps.course.views.live_session.PlugNMeetClient')
def test_student_participant_receives_limited_token(self, mock_client_cls):
mock_client = mock_client_cls.return_value
mock_client.get_join_token.return_value = {'token': 'student-token'}
session = CourseLiveSession.objects.create(
course=self.course,
subject='Session',
started_at=timezone.now(),
room_id='room-456',
)
Participant.objects.create(course=self.course, student=self.student)
self.client.force_authenticate(user=self.student)
url = reverse('course-live-session-token')
response = self.client.post(url, {'room_id': session.room_id}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
args, _ = mock_client.get_join_token.call_args
payload = args[0]
self.assertFalse(payload['user_info']['is_admin'])
metadata = payload['user_info']['user_metadata']
self.assertIn('lock_microphone', metadata['lock_settings'])
self.assertEqual(metadata.get('profilePic'), f"http://testserver{self.student.avatar.url}")
self.assertEqual(response.data['token'], 'student-token')
def test_student_without_access_cannot_get_token(self):
session = CourseLiveSession.objects.create(
course=self.course,
subject='Session',
started_at=timezone.now(),
room_id='room-789',
)
self.client.force_authenticate(user=self.student)
url = reverse('course-live-session-token')
response = self.client.post(url, {'room_id': session.room_id}, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
@mock.patch('apps.course.views.course.PlugNMeetClient')
def test_validate_metadata_includes_active_room_for_student(self, mock_client_cls):
mock_client = mock_client_cls.return_value
mock_client.is_room_active.return_value = {'status': True, 'msg': 'room is active', 'isActive': True}
mock_client.get_join_token.return_value = {'token': 'joined-student-token'}
session = CourseLiveSession.objects.create(
course=self.course,
subject='Session Live',
started_at=timezone.now(),
room_id='room-live-1',
)
Participant.objects.create(course=self.course, student=self.student)
self.client.force_authenticate(user=self.student)
url = reverse(
'course-online-validate',
kwargs={'slug': get_course_slug_value(self.course)},
)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
metadata = response.data['metadata']
self.assertTrue(metadata['is_online'])
self.assertEqual(metadata['active_room_id'], session.room_id)
self.assertTrue(metadata['can_join_live_session'])
self.assertEqual(metadata['live_session']['room_id'], session.room_id)
self.assertIsNotNone(metadata['live_session']['started_at'])
self.assertEqual(
response.data['redirect_path'],
'http://testserver/?access_token=joined-student-token'
)
self.assertEqual(
metadata['redirect_path'],
'http://testserver/?access_token=joined-student-token'
)
@mock.patch('apps.course.views.course.PlugNMeetClient')
def test_validate_metadata_for_professor_hides_creation_when_online(self, mock_client_cls):
mock_client = mock_client_cls.return_value
mock_client.is_room_active.return_value = {'status': True, 'msg': 'room is active', 'isActive': True}
mock_client.get_join_token.return_value = {'token': 'joined-prof-token'}
CourseLiveSession.objects.create(
course=self.course,
subject='Session Live',
started_at=timezone.now(),
room_id='room-live-2',
)
self.client.force_authenticate(user=self.professor)
url = reverse(
'course-online-validate',
kwargs={'slug': get_course_slug_value(self.course)},
)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
metadata = response.data['metadata']
self.assertFalse(metadata['can_create_live_session'])
self.assertEqual(
response.data['redirect_path'],
'http://testserver/?access_token=joined-prof-token'
)
@mock.patch('apps.course.views.course.PlugNMeetClient')
def test_validate_returns_direct_access_token_for_professor_when_class_is_not_online(
self,
mock_client_cls,
):
mock_client = mock_client_cls.return_value
mock_client.create_room.return_value = {'status': 'success'}
mock_client.get_join_token.return_value = {'token': 'teacher-access-token'}
self.client.force_authenticate(user=self.professor)
url = reverse(
'course-online-validate',
kwargs={'slug': get_course_slug_value(self.course)},
)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
metadata = response.data['metadata']
self.assertFalse(metadata['is_online'])
self.assertTrue(metadata['can_create_live_session'])
self.assertEqual(
response.data['redirect_path'],
'http://testserver/?access_token=teacher-access-token',
)
self.assertEqual(metadata['redirect_path'], response.data['redirect_path'])
self.assertTrue(
CourseLiveSession.objects.filter(course=self.course, ended_at__isnull=True).exists()
)
@mock.patch(
'apps.course.views.course.CourseOnlineClassTokenValidateAPIView._generate_entry_token',
return_value='waiting-token',
)
def test_validate_returns_waiting_redirect_when_class_is_not_online(self, _mock_generate_entry_token):
Participant.objects.create(course=self.course, student=self.student)
self.client.force_authenticate(user=self.student)
url = reverse(
'course-online-validate',
kwargs={'slug': get_course_slug_value(self.course)},
)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
metadata = response.data['metadata']
self.assertFalse(metadata['is_online'])
self.assertFalse(metadata['can_join_live_session'])
self.assertEqual(response.data['redirect_path'], 'http://testserver/?token=waiting-token&slug=sample-course')
self.assertIn('&slug=sample-course', response.data['redirect_path'])
self.assertEqual(metadata['redirect_path'], response.data['redirect_path'])