Skip to content

Commit

Permalink
Allow root to create OH Sections (#446)
Browse files Browse the repository at this point in the history
* Allow root to create OH Sections

* Fix Create OH Section Tests

* Reach 100% Coverage by Removing Unused Function

* Create Add Instructor API Endpoint
  • Loading branch information
ajaygandecha authored May 15, 2024
1 parent ff80dd5 commit 4406b3e
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 17 deletions.
24 changes: 24 additions & 0 deletions backend/api/academics/section_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from ..authentication import registered_user

from ...models.academics.section_member import SectionMember
from ...models.academics.section_member_details import SectionMemberDetails
from ...models.office_hours.section import OfficeHoursSection
from ...models.roster_role import RosterRole
from ...models import User

from ...services.academics import SectionMemberService
Expand Down Expand Up @@ -122,3 +124,25 @@ def check_instructor_memberships(
"""

return section_member_svc.search_instructor_memberships(subject)


@api.post(
"/instructor/{section_id}/{user_id}",
response_model=SectionMember,
tags=["Academics"],
)
def add_instructor(
section_id: int,
user_id: int,
subject: User = Depends(registered_user),
section_member_svc: SectionMemberService = Depends(),
) -> SectionMemberDetails:
"""
Gets one section by its id
Returns:
SectionDetails: Section with the given ID
"""
return section_member_svc.add_section_member(
subject, section_id, user_id, RosterRole.INSTRUCTOR
)
30 changes: 30 additions & 0 deletions backend/services/academics/section_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,36 @@ def get_section_member_by_user_id_and_oh_section_id(

return entity.to_flat_model()

def add_section_member(
self, subject: User, section_id: int, user_id: int, member_role: RosterRole
) -> SectionMemberDetails:
"""Add one member to a section
Args:
subject (User): The user for whom to add section memberships.
section_id (int): ID of the section to add a member to.
user_id (int): ID of the user to add a member to.
Returns:
SectionMember: Newly created section member.
Raises:
ResourceNotFoundException: If no academic section is found for any of the specified office hours sections.
"""
self._permission_svc.enforce(
subject, "academics.section_member.create", f"section/{section_id}"
)

draft = SectionMemberDraft(
user_id=user_id, section_id=section_id, member_role=member_role
)
section_membership = SectionMemberEntity.from_draft_model(draft)

self._session.add(section_membership)
self._session.commit()

return section_membership.to_details_model()

def add_user_section_memberships_by_oh_sections(
self,
subject: User,
Expand Down
22 changes: 14 additions & 8 deletions backend/services/office_hours/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ...models.office_hours.section_details import OfficeHoursSectionDetails
from ...models.user import User
from ..exceptions import ResourceNotFoundException
from ...services.permission import PermissionService


__authors__ = ["Sadie Amato", "Madelyn Andrews", "Bailey DeSouza", "Meghan Sun"]
Expand All @@ -39,9 +40,11 @@ class OfficeHoursSectionService:
def __init__(
self,
session: Session = Depends(db_session),
permission_svc: PermissionService = Depends(),
):
"""Initializes the database session."""
self._session = session
self._permission_svc = permission_svc

def create(
self,
Expand Down Expand Up @@ -75,17 +78,20 @@ def create(
if entity.office_hours_id is not None:
raise Exception("Office Hours Section Already Exists!")

# 3. Check If User is a Member in a Section and Has Proper Role to Create
for academic_id in academic_section_ids:
section_member_entity = self._check_membership_by_user_id_section_id(
subject.id, academic_id
)
has_permissions = self._permission_svc.check(subject, "oh_service.create", "*")

if section_member_entity.member_role != RosterRole.INSTRUCTOR:
raise PermissionError(
f"Section Member is not an Instructor. User Does Not Have Permisions Create an Office Hours Section."
# 3. Check If User is a Member in a Section and Has Proper Role to Create
if not has_permissions:
for academic_id in academic_section_ids:
section_member_entity = self._check_membership_by_user_id_section_id(
subject.id, academic_id
)

if section_member_entity.member_role != RosterRole.INSTRUCTOR:
raise PermissionError(
f"Section Member is not an Instructor. User Does Not Have Permisions Create an Office Hours Section."
)

# Query and Execution to Update All Academic Section With New OH Section ID
oh_section_entity = OfficeHoursSectionEntity.from_draft_model(oh_section)

Expand Down
27 changes: 25 additions & 2 deletions backend/test/services/academics/section_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

from unittest.mock import create_autospec
import pytest
from backend.models.roster_role import RosterRole
from backend.services.exceptions import (
ResourceNotFoundException,
UserPermissionException,
)
from backend.services.permission import PermissionService
from ....services.academics import SectionService
from ....services.academics import SectionService, SectionMemberService
from ....models.academics import SectionDetails

# Imported fixtures provide dependencies injected for the tests as parameters.
from .fixtures import permission_svc, section_svc
from .fixtures import permission_svc, section_svc, section_member_svc

# Import the setup_teardown fixture explicitly to load entities in database
from ..core_data import setup_insert_data_fixture as insert_order_0
Expand Down Expand Up @@ -231,3 +232,25 @@ def test_get_sections_with_no_office_hours_by_term(section_svc: SectionService):

assert len(sections_with_no_oh) > 0
assert isinstance(sections_with_no_oh[0], SectionDetails)


def test_root_add_section_member(section_member_svc: SectionMemberService):
membership = section_member_svc.add_section_member(
subject=user_data.root,
section_id=section_data.comp_101_001.id,
user_id=user_data.root.id,
member_role=RosterRole.INSTRUCTOR,
)
assert membership is not None


def test_user_add_section_member(section_member_svc: SectionMemberService):

with pytest.raises(UserPermissionException):
section_member_svc.add_section_member(
subject=user_data.student,
section_id=section_data.comp_101_001.id,
user_id=user_data.root.id,
member_role=RosterRole.INSTRUCTOR,
)
pytest.fail()
4 changes: 2 additions & 2 deletions backend/test/services/office_hours/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ def oh_ticket_svc(


@pytest.fixture()
def oh_section_svc(session: Session):
def oh_section_svc(session: Session, permission_svc: PermissionService):
"""OfficeHoursSectionService fixture."""
return OfficeHoursSectionService(session)
return OfficeHoursSectionService(session, permission_svc)
80 changes: 75 additions & 5 deletions backend/test/services/office_hours/section/create_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

from .....models.office_hours.section_details import OfficeHoursSectionDetails
from .....services.academics.section import SectionService

from .....services.permission import PermissionService
from .....services.exceptions import ResourceNotFoundException
from .....services.office_hours.section import OfficeHoursSectionService

# Imported fixtures provide dependencies injected for the tests as parameters.
from ..fixtures import permission_svc, oh_section_svc
from ..fixtures import permission_svc
from ..fixtures import oh_section_svc
from ...academics.fixtures import section_svc

# Import the setup_teardown fixture explicitly to load entities in database
Expand All @@ -22,6 +23,8 @@

# Import the fake model data in a namespace for test assertions
from .. import office_hours_data
from ...user_data import root

from ...academics.section_data import (
user__comp110_instructor,
user__comp110_student_0,
Expand All @@ -38,6 +41,73 @@
__license__ = "MIT"


def test_create_by_root(
oh_section_svc: OfficeHoursSectionService, section_svc: SectionService
):
"""Test case to validate creation of an office hours section by an instructor."""
oh_section = oh_section_svc.create(
root,
office_hours_data.oh_section_draft,
[comp_301_001_current_term.id],
)

assert isinstance(oh_section, OfficeHoursSectionDetails)
assert oh_section.title == office_hours_data.oh_section_draft.title


def test_create_by_root_and_linked_to_academic_section(
oh_section_svc: OfficeHoursSectionService, section_svc: SectionService
):
"""Test case to validate creation of an office hours section linked to an academic section by an instructor."""
oh_section = oh_section_svc.create(
root,
office_hours_data.oh_section_draft,
[comp_301_001_current_term.id],
)

assert isinstance(oh_section, OfficeHoursSectionDetails)

# Check if OH Section Is Linked to Academic Section
academic_section = section_svc.get_by_id(comp_301_001_current_term.id)
assert oh_section.id == academic_section.office_hours_section.id


def test_create_by_root_multiple_academic_sections(
oh_section_svc: OfficeHoursSectionService,
):
"""Test case to validate creation of an office hours section with multiple academic sections by an instructor."""
oh_section = oh_section_svc.create(
root,
office_hours_data.oh_section_draft,
[comp_301_001_current_term.id, comp_210_001_current_term.id],
)

assert isinstance(oh_section, OfficeHoursSectionDetails)
assert oh_section.title == office_hours_data.oh_section_draft.title


def test_create_by_root_multiple_academic_sections_and_linked_to_academic_section(
oh_section_svc: OfficeHoursSectionService, section_svc: SectionService
):
"""Test case to validate creation of an office hours section with multiple academic sections
and linked to those academic sections by an instructor."""
oh_section = oh_section_svc.create(
root,
office_hours_data.oh_section_draft,
[comp_301_001_current_term.id, comp_210_001_current_term.id],
)

assert isinstance(oh_section, OfficeHoursSectionDetails)
assert oh_section.title == office_hours_data.oh_section_draft.title

# Check if OH Section Is Linked to Academic Sections
comp301 = section_svc.get_by_id(comp_301_001_current_term.id)
comp210 = section_svc.get_by_id(comp_210_001_current_term.id)

assert oh_section.id == comp301.office_hours_section.id
assert oh_section.id == comp210.office_hours_section.id


def test_create_by_instructor(
oh_section_svc: OfficeHoursSectionService, section_svc: SectionService
):
Expand Down Expand Up @@ -144,7 +214,7 @@ def test_create_exception_if_oh_section_exists(
"""Test case to validate that creating an office hours section raises an Exception if a duplicate section already exists."""
with pytest.raises(Exception):
oh_section_svc.create(
user__comp110_instructor,
root,
office_hours_data.oh_section_draft,
[comp_110_001_current_term.id],
)
Expand All @@ -157,7 +227,7 @@ def test_create_exception_if_one_oh_section_exists_and_other_does_not_have_one(
"""Test case to validate that creating an office hours section raises an Exception if one section exists but the other doesn't."""
with pytest.raises(Exception):
oh_section_svc.create(
user__comp110_instructor,
root,
office_hours_data.oh_section_draft,
[comp_110_001_current_term.id, comp_301_001_current_term.id],
)
Expand All @@ -170,7 +240,7 @@ def test_create_exception_if_can_not_find_all_academic_sections(
"""Test case to validate that creating an office hours section raises an ResourceNotFoundException if all academic sections are not found."""
with pytest.raises(ResourceNotFoundException):
oh_section_svc.create(
user__comp110_instructor,
root,
office_hours_data.oh_section_draft,
[comp_301_001_current_term.id, 99],
)
Expand Down

0 comments on commit 4406b3e

Please sign in to comment.