1052 lines
48 KiB
Python

import base64
# pyrefly: ignore [missing-import]
from odoo.tests.common import TransactionCase
# pyrefly: ignore [missing-import]
from odoo.exceptions import AccessError, ValidationError
from datetime import date, timedelta
# pyrefly: ignore [missing-import]
from odoo import fields
class TestEducationLMS(TransactionCase):
def setUp(self):
super(TestEducationLMS, self).setUp()
self.env.user.tz = 'UTC'
# Groups
self.group_faculty = self.env.ref('education_lms.group_education_lms_faculty')
self.group_student = self.env.ref('education_lms.group_education_lms_student')
self.group_admin = self.env.ref('education_lms.group_education_lms_admin')
# Create Users
self.user_faculty_a = self.env['res.users'].create({
'name': 'Faculty A',
'login': 'faculty_a',
'groups_id': [(4, self.group_faculty.id)]
})
self.user_faculty_b = self.env['res.users'].create({
'name': 'Faculty B',
'login': 'faculty_b',
'groups_id': [(4, self.group_faculty.id)]
})
self.user_student_a = self.env['res.users'].create({
'name': 'Student A',
'login': 'student_a',
'groups_id': [(4, self.group_student.id), (4, self.env.ref('education_student.group_education_student_user').id)]
})
self.user_student_b = self.env['res.users'].create({
'name': 'Student B',
'login': 'student_b',
'groups_id': [(4, self.group_student.id), (4, self.env.ref('education_student.group_education_student_user').id)]
})
# Academic structure
self.campus = self.env['education.campus'].create({'name': 'Campus A', 'code': 'CA'})
self.department = self.env['education.department'].create({
'name': 'Dept A', 'code': 'DA', 'campus_id': self.campus.id
})
self.level = self.env['education.academic.level'].create({'name': 'Level A', 'code': 'LA'})
self.program = self.env['education.program'].create({
'name': 'Program A', 'code': 'PA', 'department_id': self.department.id, 'level_id': self.level.id
})
self.academic_year = self.env['education.academic.year'].create({
'name': 'Year 2024',
'code': 'Y24',
'start_date': date(2024, 1, 1),
'end_date': date(2024, 12, 31)
})
self.term = self.env['education.academic.term'].create({
'name': 'Term 1', 'code': 'T1', 'academic_year_id': self.academic_year.id,
'start_date': date(2024, 1, 1), 'end_date': date(2024, 6, 30)
})
self.batch = self.env['education.batch'].create({
'name': 'Batch A', 'code': 'BA'
})
self.semester = self.env['education.semester'].create({
'name': 'Sem 1', 'code': 'S1'
})
self.section = self.env['education.section'].create({'name': 'Section A', 'code': 'SECA'})
self.subject = self.env['education.subject'].create({
'name': 'Subject A', 'code': 'SUB_A', 'program_id': self.program.id
})
# Create Students
self.student_a = self.env['education.student'].create({
'first_name': 'Student',
'last_name': 'A',
'portal_user_id': self.user_student_a.id,
'campus_id': self.campus.id,
'department_id': self.department.id,
'program_id': self.program.id,
'level_id': self.level.id,
'academic_year_id': self.academic_year.id,
'academic_term_id': self.term.id,
'batch_id': self.batch.id,
'semester_id': self.semester.id,
'section_id': self.section.id,
'admission_type': 'regular'
})
self.student_b = self.env['education.student'].create({
'first_name': 'Student',
'last_name': 'B',
'portal_user_id': self.user_student_b.id,
'campus_id': self.campus.id,
'department_id': self.department.id,
'program_id': self.program.id,
'level_id': self.level.id,
'academic_year_id': self.academic_year.id,
'academic_term_id': self.term.id,
'batch_id': self.batch.id,
'semester_id': self.semester.id,
'section_id': self.section.id,
'admission_type': 'regular'
})
# Courses
self.course_faculty_a = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course A',
'subject_id': self.subject.id,
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'term_id': self.term.id,
'batch_id': self.batch.id,
'state': 'draft'
})
self.course_faculty_b = self.env['education.lms.course'].with_user(self.user_faculty_b).create({
'name': 'Course B',
'subject_id': self.subject.id,
'faculty_id': self.user_faculty_b.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'state': 'draft'
})
self.course_faculty_a.write({'state': 'published'})
self.course_faculty_b.write({'state': 'published'})
# 1. Faculty A creates assignment on Faculty A course -> PASS
def test_01_faculty_creates_own_assignment(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'assigned_date': fields.Date.today(),
'due_date': fields.Date.today() + timedelta(days=5),
'maximum_marks': 100
})
self.assertTrue(assignment.id)
# 2. Faculty A creates assignment on Faculty B course -> REJECT
def test_02_faculty_creates_other_assignment(self):
with self.assertRaises(AccessError):
self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign B',
'course_id': self.course_faculty_b.id,
'assigned_date': fields.Date.today(),
'due_date': fields.Date.today() + timedelta(days=5)
})
# 3. Faculty B edits Faculty A assignment -> REJECT
def test_03_faculty_edits_other_assignment(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
with self.assertRaises(AccessError):
assignment.with_user(self.user_faculty_b).write({'name': 'Hacked'})
# 4. Faculty B deletes Faculty A assignment -> REJECT
def test_04_faculty_deletes_other_assignment(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
with self.assertRaises(AccessError):
assignment.with_user(self.user_faculty_b).unlink()
# 5. Faculty A publishes assignment under Draft course -> REJECT
def test_05_publish_under_draft_course(self):
self.course_faculty_a.with_user(self.user_faculty_a).write({'state': 'draft'})
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
with self.assertRaises(ValidationError):
assignment.with_user(self.user_faculty_a).write({'state': 'published'})
# 6. Faculty A publishes assignment under Published course -> PASS
def test_06_publish_under_published_course(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.with_user(self.user_faculty_a).write({'state': 'published'})
self.assertEqual(assignment.state, 'published')
# 7. due_date before assigned_date -> REJECT
def test_07_invalid_dates(self):
with self.assertRaises(ValidationError):
self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'assigned_date': fields.Date.today(),
'due_date': fields.Date.today() - timedelta(days=1)
})
# 8. maximum_marks <= 0 -> REJECT
def test_08_invalid_marks(self):
with self.assertRaises(ValidationError):
self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'maximum_marks': 0
})
# 9. Section from another course selected -> REJECT
def test_09_invalid_section(self):
section_b = self.env['education.lms.section'].create({
'name': 'Section B', 'course_id': self.course_faculty_b.id
})
with self.assertRaises(ValidationError):
self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'section_id': section_b.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
# 10. Lesson from another section/course selected -> REJECT
def test_10_invalid_lesson(self):
section_a = self.env['education.lms.section'].create({
'name': 'Section A', 'course_id': self.course_faculty_a.id
})
section_b = self.env['education.lms.section'].create({
'name': 'Section B', 'course_id': self.course_faculty_b.id
})
lesson_b = self.env['education.lms.lesson'].create({
'name': 'Lesson B', 'section_id': section_b.id, 'course_id': self.course_faculty_b.id
})
with self.assertRaises(ValidationError):
self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'section_id': section_a.id,
'lesson_id': lesson_b.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
# 11. Student A creates submission for eligible assignment -> PASS
def test_11_student_creates_submission(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
self.assertTrue(submission.id)
# 12. Student A creates submission for Student B -> REJECT
def test_12_student_creates_for_other(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
with self.assertRaises(AccessError):
self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_b.id
})
# 13. Student A submits assignment for an ineligible course -> REJECT
def test_13_student_ineligible_course(self):
# Create course for different program
program2 = self.env['education.program'].create({
'name': 'Program 2', 'code': 'P2', 'department_id': self.department.id, 'level_id': self.level.id
})
course_ineligible = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course 2',
'subject_id': self.subject.id,
'faculty_id': self.user_faculty_a.id,
'program_id': program2.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'state': 'published'
})
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 2',
'course_id': course_ineligible.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
with self.assertRaises(ValidationError):
self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
# 14. Student A submits with no text and no attachment -> REJECT
def test_14_submit_without_content(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
with self.assertRaises(ValidationError):
submission.with_user(self.user_student_a).write({'status': 'submitted'})
# 15. Student A submits before due date -> status Submitted
def test_15_submit_before_due_date(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
submission.with_user(self.user_student_a).write({'status': 'submitted', 'text_submission': 'Done'})
self.assertEqual(submission.status, 'submitted')
self.assertFalse(submission.is_late)
# 16. Student A submits after due date -> status Late
def test_16_submit_after_due_date(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'assigned_date': fields.Date.today() - timedelta(days=2),
'due_date': fields.Date.today() - timedelta(days=1)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
submission.with_user(self.user_student_a).write({'status': 'submitted', 'text_submission': 'Done'})
self.assertEqual(submission.status, 'late')
self.assertTrue(submission.is_late)
# 17. Student A cannot edit another student's submission -> REJECT
def test_17_student_edit_other_submission(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
with self.assertRaises(AccessError):
submission.with_user(self.user_student_b).write({'text_submission': 'Hacked'})
# 18. Faculty A can view/evaluate Student A submission for Faculty A course -> PASS
def test_18_faculty_evaluates_submission(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
submission.with_user(self.user_faculty_a).write({'status': 'evaluated', 'marks': 90})
self.assertEqual(submission.status, 'evaluated')
self.assertEqual(submission.marks, 90)
self.assertTrue(submission.evaluation_date)
# 19. Faculty B cannot view/evaluate that submission -> REJECT
def test_19_faculty_b_evaluates_faculty_a_submission(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
with self.assertRaises(AccessError):
submission.with_user(self.user_faculty_b).write({'status': 'evaluated', 'marks': 90})
# 20. Faculty evaluates with marks = 0 -> PASS
def test_20_faculty_evaluate_zero_marks(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
submission.with_user(self.user_faculty_a).write({'status': 'evaluated', 'marks': 0.0})
self.assertEqual(submission.status, 'evaluated')
self.assertEqual(submission.marks, 0.0)
# 21. Faculty evaluates with marks > maximum_marks -> REJECT
def test_21_faculty_evaluate_exceed_max_marks(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'maximum_marks': 100
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
with self.assertRaises(ValidationError):
submission.with_user(self.user_faculty_a).write({'status': 'evaluated', 'marks': 105})
# 22. Faculty evaluates with negative marks -> REJECT
def test_22_faculty_evaluate_negative_marks(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
with self.assertRaises(ValidationError):
submission.with_user(self.user_faculty_a).write({'status': 'evaluated', 'marks': -5})
# 23. Empty marks remain unevaluated; never convert empty to 0.
def test_23_empty_marks(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
self.assertFalse(submission.marks)
# 24. Duplicate submission for same student + assignment -> REJECT
def test_24_duplicate_submission(self):
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
# Try to create another one
with self.assertRaises(Exception):
self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
# 25. Admin can manage all assignments and submissions -> PASS
def test_25_admin_access(self):
user_admin = self.env['res.users'].create({
'name': 'Admin User',
'login': 'lms_admin',
'groups_id': [(4, self.group_admin.id)]
})
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign 1',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.with_user(user_admin).write({'title': 'Admin Edited'})
self.assertEqual(assignment.title, 'Admin Edited')
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
submission.with_user(user_admin).write({'marks': 100, 'status': 'evaluated'})
self.assertEqual(submission.marks, 100)
# PHASE G TESTS
# 26. Draft -> Published
def test_26_draft_to_published(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
self.assertEqual(assignment.state, 'draft')
assignment.action_publish()
self.assertEqual(assignment.state, 'published')
# 27. Published -> Closed
def test_27_published_to_closed(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
assignment.action_close()
self.assertEqual(assignment.state, 'closed')
# 28. Closed -> Published rejected
def test_28_closed_to_published_rejected(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
assignment.action_close()
with self.assertRaises(ValidationError):
assignment.write({'state': 'published'})
# 29. Closed -> Draft rejected
def test_29_closed_to_draft_rejected(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
assignment.action_close()
with self.assertRaises(ValidationError):
assignment.write({'state': 'draft'})
# 30. Closed assignment rejects new submission
def test_30_closed_assignment_rejects_new_submission(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
assignment.action_close()
with self.assertRaises(ValidationError):
self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id
})
# 31. Faculty evaluation succeeds on own course
def test_31_faculty_evaluation_own_course(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
submission.with_user(self.user_faculty_a).write({'marks': 10, 'status': 'evaluated'})
self.assertEqual(submission.status, 'evaluated')
# 32. Cross-faculty evaluation rejected
def test_32_cross_faculty_evaluation_rejected(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
with self.assertRaises(AccessError):
submission.with_user(self.user_faculty_b).write({'marks': 10, 'status': 'evaluated'})
# 33. Student cannot modify evaluation fields
def test_33_student_cannot_modify_evaluation(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
})
with self.assertRaises(AccessError):
submission.with_user(self.user_student_a).write({'marks': 10})
# 34. Faculty cannot modify another faculty's evaluation
def test_34_faculty_cannot_modify_other_faculty_eval(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
submission.with_user(self.user_faculty_a).write({'marks': 10, 'status': 'evaluated'})
with self.assertRaises(AccessError):
submission.with_user(self.user_faculty_b).write({'marks': 15})
# 35. 0 marks accepted
def test_35_0_marks_accepted(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
submission.with_user(self.user_faculty_a).write({'marks': 0.0, 'status': 'evaluated'})
self.assertEqual(submission.marks, 0.0)
# 36. empty marks rejected for Evaluated status
def test_36_empty_marks_rejected(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Phase G',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5)
})
assignment.action_publish()
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Done',
'status': 'submitted'
})
with self.assertRaises(ValidationError):
submission.with_user(self.user_faculty_a).write({'marks': False, 'status': 'evaluated'})
# ==========================================
# PHASE H SECURITY TESTS
# ==========================================
# 37. Faculty A cannot read/modify Faculty B courses/content
def test_37_faculty_isolation(self):
# Faculty B course
course_b = self.env['education.lms.course'].with_user(self.user_faculty_b).create({
'name': 'Course B',
'faculty_id': self.user_faculty_b.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
})
# Faculty A tries to read -> AccessError
with self.assertRaises(AccessError):
course_b.with_user(self.user_faculty_a).read(['name'])
# Faculty A tries to modify
with self.assertRaises(AccessError):
course_b.with_user(self.user_faculty_a).write({'name': 'Hacked'})
# Faculty A tries to delete
with self.assertRaises(AccessError):
course_b.with_user(self.user_faculty_a).unlink()
# Faculty A tries to create section under Course B -> AccessError
with self.assertRaises(AccessError):
self.env['education.lms.section'].with_user(self.user_faculty_a).create({
'name': 'Sec B',
'course_id': course_b.id
})
# 38. Student Isolation (cannot read/modify other student submission)
def test_38_student_isolation(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign H',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'state': 'published'
})
submission_a = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Student A Work',
})
# Student B tries to read
with self.assertRaises(AccessError):
submission_a.with_user(self.user_student_b).read(['text_submission'])
# Student B tries to write
with self.assertRaises(AccessError):
submission_a.with_user(self.user_student_b).write({'text_submission': 'Hacked'})
# Student B tries to create submission for Student A
assignment_new = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign H New',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'state': 'published'
})
with self.assertRaises(AccessError):
self.env['education.lms.submission'].with_user(self.user_student_b).create({
'assignment_id': assignment_new.id,
'student_id': self.student_a.id,
'text_submission': 'Student B Work',
})
# 39. Course Eligibility Security
def test_39_course_eligibility(self):
# A. Matching
course_a = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course A',
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'term_id': self.term.id,
'batch_id': self.batch.id,
'state': 'published'
})
# Student A matches program, year, term, batch
self.env.invalidate_all()
res = course_a.with_user(self.user_student_a).read(['name'])
self.assertTrue(len(res) > 0)
self.assertTrue(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_a.id)])))
# B. Different Program
program_2 = self.env['education.program'].create({
'name': 'Program 2', 'code': 'P2', 'department_id': self.department.id, 'level_id': self.level.id
})
course_b = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course B',
'faculty_id': self.user_faculty_a.id,
'program_id': program_2.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'term_id': self.term.id,
'batch_id': self.batch.id,
'state': 'published'
})
with self.assertRaises(AccessError):
course_b.with_user(self.user_student_a).read(['name'])
self.assertFalse(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_b.id)])))
# C. Different Year
year_2 = self.env['education.academic.year'].create({
'name': 'Year 2025', 'code': 'Y25', 'start_date': date(2025, 1, 1), 'end_date': date(2025, 12, 31)
})
course_c = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course C',
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': year_2.id,
'subject_id': self.subject.id,
'term_id': self.term.id,
'batch_id': self.batch.id,
'state': 'published'
})
with self.assertRaises(AccessError):
course_c.with_user(self.user_student_a).read(['name'])
self.assertFalse(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_c.id)])))
# D. Different Term
term_2 = self.env['education.academic.term'].create({
'name': 'Term 2', 'code': 'T2', 'academic_year_id': self.academic_year.id,
'start_date': date(2024, 7, 1), 'end_date': date(2024, 12, 31),
'sequence': 2
})
course_d = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course D',
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'term_id': term_2.id,
'batch_id': self.batch.id,
'state': 'published'
})
with self.assertRaises(AccessError):
course_d.with_user(self.user_student_a).read(['name'])
self.assertFalse(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_d.id)])))
# E. Course with no term -> Matches
course_e = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course E',
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'batch_id': self.batch.id,
'state': 'published'
})
res = course_e.with_user(self.user_student_a).read(['name'])
self.assertTrue(len(res) > 0)
self.assertTrue(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_e.id)])))
# F. Different batch
batch_2 = self.env['education.batch'].create({'name': 'Batch B', 'code': 'BB'})
course_f = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course F',
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'term_id': self.term.id,
'batch_id': batch_2.id,
'state': 'published'
})
with self.assertRaises(AccessError):
course_f.with_user(self.user_student_a).read(['name'])
self.assertFalse(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_f.id)])))
# G. Course with no batch
course_g = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course G',
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'term_id': self.term.id,
'state': 'published'
})
res = course_g.with_user(self.user_student_a).read(['name'])
self.assertTrue(len(res) > 0)
self.assertTrue(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_g.id)])))
# H. Course with no term AND no batch (should match)
course_h = self.env['education.lms.course'].with_user(self.user_faculty_a).create({
'name': 'Course H',
'faculty_id': self.user_faculty_a.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
'state': 'published'
})
res = course_h.with_user(self.user_student_a).read(['name'])
self.assertTrue(len(res) > 0)
self.assertTrue(bool(self.env['education.lms.course'].with_user(self.user_student_a).search([('id', '=', course_h.id)])))
# 40. Assignment access and states
def test_40_assignment_access(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign Draft',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'state': 'draft'
})
self.env.invalidate_all()
# Draft assignment -> Student A cannot read
with self.assertRaises(AccessError):
assignment.with_user(self.user_student_a).read(['title'])
self.assertFalse(bool(self.env['education.lms.assignment'].with_user(self.user_student_a).search([('id', '=', assignment.id)])))
# Published -> Student A can read
assignment.with_user(self.user_faculty_a).action_publish()
self.env.invalidate_all()
res = assignment.with_user(self.user_student_a).read(['title'])
self.assertTrue(len(res) > 0)
self.assertTrue(bool(self.env['education.lms.assignment'].with_user(self.user_student_a).search([('id', '=', assignment.id)])))
# Closed -> Student A can read, but cannot submit
assignment.with_user(self.user_faculty_a).action_close()
self.env.invalidate_all()
res = assignment.with_user(self.user_student_a).read(['title'])
self.assertTrue(len(res) > 0)
with self.assertRaises(ValidationError):
self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Student A Work',
})
# 41. Submission edge cases
def test_41_submission_security_edge_cases(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign H2',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'state': 'published'
})
submission = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'Student A Work',
})
# Spoof student_id
with self.assertRaises(AccessError):
submission.with_user(self.user_student_a).write({'student_id': self.student_b.id})
# Spoof assignment_id
assignment_2 = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign H3',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'state': 'published'
})
with self.assertRaises(AccessError):
submission.with_user(self.user_student_a).write({'assignment_id': assignment_2.id})
submission.with_user(self.user_student_a).action_submit()
# Change marks
with self.assertRaises(AccessError):
submission.with_user(self.user_student_a).write({'marks': 100})
# Change feedback
with self.assertRaises(AccessError):
submission.with_user(self.user_student_a).write({'feedback': 'Good job me'})
# Change status
with self.assertRaises(AccessError):
submission.with_user(self.user_student_a).write({'status': 'evaluated'})
# Change evaluation metadata
with self.assertRaises(AccessError):
submission.with_user(self.user_student_a).write({'evaluated_by': self.user_student_a.id})
# Faculty B cannot evaluate Faculty A's submissions
with self.assertRaises(AccessError):
submission.with_user(self.user_faculty_b).write({'marks': 50})
# Faculty A CAN evaluate
submission.with_user(self.user_faculty_a).write({'marks': 50, 'status': 'evaluated'})
self.assertEqual(submission.marks, 50)
# 42. Attachment Security Direct Test
def test_42_attachment_security(self):
self.course_faculty_a.state = 'published'
assignment = self.env['education.lms.assignment'].with_user(self.user_faculty_a).create({
'title': 'Assign H Attachments',
'course_id': self.course_faculty_a.id,
'due_date': fields.Date.today() + timedelta(days=5),
'state': 'published'
})
# Student A submission
sub_a = self.env['education.lms.submission'].with_user(self.user_student_a).create({
'assignment_id': assignment.id,
'student_id': self.student_a.id,
'text_submission': 'A',
})
attach_a = self.env['ir.attachment'].with_user(self.user_student_a).create({
'name': 'file_a.txt',
'datas': base64.b64encode(b'student A file'),
'res_model': 'education.lms.submission',
'res_id': sub_a.id,
})
sub_b = self.env['education.lms.submission'].with_user(self.user_student_b).create({
'assignment_id': assignment.id,
'student_id': self.student_b.id,
'text_submission': 'B',
})
attach_b = self.env['ir.attachment'].with_user(self.user_student_b).create({
'name': 'file_b.txt',
'datas': base64.b64encode(b'student B file'),
'res_model': 'education.lms.submission',
'res_id': sub_b.id,
})
# Verify A can read attach A binary data
res_a = attach_a.with_user(self.user_student_a).read(['raw', 'datas'])
self.assertTrue(len(res_a) > 0)
# Verify A cannot read attach B binary data
with self.assertRaises(AccessError):
attach_b.with_user(self.user_student_a).read(['raw', 'datas'])
# Verify Faculty A can read both
res_fa1 = attach_a.with_user(self.user_faculty_a).read(['raw'])
self.assertTrue(len(res_fa1) > 0)
res_fa2 = attach_b.with_user(self.user_faculty_a).read(['raw'])
self.assertTrue(len(res_fa2) > 0)
# Verify Faculty B cannot read either
with self.assertRaises(AccessError):
attach_a.with_user(self.user_faculty_b).read(['raw'])
with self.assertRaises(AccessError):
attach_b.with_user(self.user_faculty_b).read(['raw'])
# 43. Admin override
def test_43_admin_override(self):
user_admin = self.env['res.users'].create({
'name': 'Admin User 2',
'login': 'lms_admin2',
'groups_id': [(4, self.group_admin.id)]
})
# Admin can read faculty B's course
course_b = self.env['education.lms.course'].with_user(self.user_faculty_b).create({
'name': 'Course B',
'faculty_id': self.user_faculty_b.id,
'program_id': self.program.id,
'academic_year_id': self.academic_year.id,
'subject_id': self.subject.id,
})
res = course_b.with_user(user_admin).read(['name'])
self.assertTrue(len(res) > 0)
course_b.with_user(user_admin).write({'name': 'Admin changed'})
self.assertEqual(course_b.name, 'Admin changed')
# 44. Portal User Mapping
def test_44_portal_user_mapping(self):
# A user without student_id should not see Student A's courses/submissions in LMS Context
user_no_student = self.env['res.users'].create({
'name': 'No Student',
'login': 'no_student',
'groups_id': [(4, self.group_student.id)]
})
self.course_faculty_a.state = 'published'
with self.assertRaises(AccessError):
self.course_faculty_a.with_user(user_no_student).read(['name'])