def test_create_nonunique_unit_requirements(self, db): """Requires unit requirements in a single template to have unique names.""" coe_advisor_id = AuthorizedUser.get_id_per_uid(coe_advisor_uid) template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=coe_advisor_id, degree_name='Celtic Studies BA 2021', ) name = 'Celtic Requirements' unit_requirement = DegreeProgressUnitRequirement.create( created_by=coe_advisor_id, min_units=70, name=name, template_id=template.id, ) assert unit_requirement with pytest.raises(IntegrityError): DegreeProgressUnitRequirement.create( created_by=coe_advisor_id, min_units=53, name=name, template_id=template.id, ) db.session.rollback()
def test_create_unit_requirement(self): coe_advisor_id = AuthorizedUser.get_id_per_uid(coe_advisor_uid) template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=coe_advisor_id, degree_name='Celtic Studies BA 2021', ) min_units = 45 name = 'Celtic Requirements' unit_requirement = DegreeProgressUnitRequirement.create( created_by=coe_advisor_id, min_units=min_units, name=name, template_id=template.id, ) assert unit_requirement assert unit_requirement.__repr__( ) == f"""<DegreeProgressUnitRequirement id={unit_requirement.id}, name={name}, min_units={min_units}, template_id={template.id}, created_at={unit_requirement.created_at}, created_by={coe_advisor_id}, updated_at={unit_requirement.updated_at}, updated_by={coe_advisor_id}>""" assert template.unit_requirements assert len(template.unit_requirements) == 1 assert unit_requirement.template
def mock_degree_check(): return DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), degree_name=f'Degree check of {coe_student_sid}', student_sid='11667051', )
def test_update_template(self, client, fake_auth): """Authorized user can edit a template.""" user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(user.uid) template = DegreeProgressTemplate.create( advisor_dept_codes=get_dept_codes(user), created_by=user.id, degree_name='Boogie Down Productions', ) for index in (1, 2, 3): DegreeProgressUnitRequirement.create( created_by=user.id, min_units=index, name=f'Unit Requirement #{index}', template_id=template.id, ) updated_name = 'KRS One' api_json = self._api_clone_template(client=client, name=updated_name, template_id=template.id) assert api_json['id'] != template.id assert api_json['name'] == updated_name assert len(api_json['unitRequirements']) == 3
def test_delete(self, client, fake_auth): """COE advisor can delete template.""" fake_auth.login(coe_advisor_read_write_uid) user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) assert user.degree_progress_permission == 'read_write' template = DegreeProgressTemplate.create(['COENG'], user.id, f'Classical Civilizations, by {user.id}') assert client.delete(f'/api/degree/{template.id}').status_code == 200 assert client.get(f'/api/degree/{template.id}').status_code == 404
def mock_template(): user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) marker = datetime.now().timestamp() return DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user.id, degree_name=f'I am a mock template, made for a mock category ({marker})', )
def mock_degree_check(): user_id = AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid) parent_template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user_id, degree_name='Zoology BS 2021', ) degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user_id, degree_name='Zoology BS 2021', parent_template_id=parent_template.id, student_sid=coe_student_sid, ) std_commit(allow_test_environment=True) yield degree_check # Avoid polluting other tests DegreeProgressTemplate.delete(degree_check.id) std_commit(allow_test_environment=True)
def create_degree(): params = request.get_json() name = get_param(params, 'name', None) validate_template_upsert(name=name) degree = DegreeProgressTemplate.create( advisor_dept_codes=dept_codes_where_advising(current_user), created_by=current_user.get_id(), degree_name=name, ) return tolerant_jsonify(degree.to_api_json())
def mock_degree_checks(): user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) marker = datetime.now().timestamp() degree_checks = [] for index in (1, 2, 3): parent_template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user.id, degree_name=f'I am a mock template ({marker}_{index})', ) degree_checks.append( DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user.id, degree_name=f'I am a mock degree check ({marker}_{index})', student_sid=coe_student_sid, parent_template_id=parent_template.id, ), ) std_commit(allow_test_environment=True) return degree_checks
def mock_note(): user = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=user.id, degree_name='I am a mock template, made for a mock note', ) return DegreeProgressNote.upsert( body='A mock note.', template_id=template.id, updated_by=user.id, )
def mock_unit_requirement(): template = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), degree_name='Ursinology 2025', ) return DegreeProgressUnitRequirement.create( created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), min_units=16, name='Aureum Ursi', template_id=template.id, )
def clone(template, created_by, name=None, sid=None): clone = DegreeProgressTemplate.create( advisor_dept_codes=dept_codes_where_advising(current_user), created_by=created_by, degree_name=name or template.degree_name, parent_template_id=template.id if sid else None, student_sid=sid, ) unit_requirements_by_source_id = {} for unit_requirement in template.unit_requirements: source_id = unit_requirement.id unit_requirements_by_source_id[ source_id] = DegreeProgressUnitRequirement.create( created_by=created_by, min_units=unit_requirement.min_units, name=unit_requirement.name, template_id=clone.id, ) def _create_category(category_, parent_id): unit_requirement_ids = [] for u in category_['unitRequirements']: source_id_ = u['id'] cross_reference = unit_requirements_by_source_id[source_id_] unit_requirement_ids.append(cross_reference.id) return DegreeProgressCategory.create( category_type=category_['categoryType'], name=category_['name'], position=category_['position'], template_id=clone.id, course_units_lower=category_['unitsLower'], course_units_upper=category_['unitsUpper'], description=category_['description'], parent_category_id=parent_id, unit_requirement_ids=unit_requirement_ids, ) for category in DegreeProgressCategory.get_categories( template_id=template.id): c = _create_category(category_=category, parent_id=None) for course in category['courseRequirements']: _create_category(category_=course, parent_id=c.id) for subcategory in category['subcategories']: s = _create_category(category_=subcategory, parent_id=c.id) for course in subcategory['courseRequirements']: _create_category(category_=course, parent_id=s.id) # TODO: Unit requirements? return DegreeProgressTemplate.find_by_id(clone.id)
def test_assign_and_unassign_course(self, client, fake_auth): """User can assign and unassign a course.""" advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(advisor.uid) sid = '11667051' # Set up degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=advisor.id, degree_name=f'Degree check for {sid}', student_sid=sid, ) original_updated_at = degree_check.updated_at category = DegreeProgressCategory.create( category_type='Category', name=f'Category for {sid}', position=1, template_id=degree_check.id, ) std_commit(allow_test_environment=True) # Assign api_json = _api_get_degree(client, degree_check_id=degree_check.id) course_id = api_json['courses']['unassigned'][-1]['id'] course = _api_assign_course(category_id=category.id, client=client, course_id=course_id) # Verify assignment api_json = _api_get_degree(client, degree_check_id=degree_check.id) assert course['categoryId'] == category.id assert course_id in [c['id'] for c in api_json['courses']['assigned']] assert course_id not in [ c['id'] for c in api_json['courses']['unassigned'] ] # Unassign _api_assign_course(category_id=None, client=client, course_id=course_id) api_json = _api_get_degree(client, degree_check_id=degree_check.id) assert course_id not in [ c['id'] for c in api_json['courses']['assigned'] ] assert course_id in [ c['id'] for c in api_json['courses']['unassigned'] ] # Verify update of updated_at assert DegreeProgressTemplate.find_by_id( degree_check.id).updated_at != original_updated_at
def test_illegal_assign(self, client, fake_auth): """A course cannot be assigned to a category with a subcategory.""" advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(advisor.uid) sid = '11667051' # Set up degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=advisor.id, degree_name=f'Degree for {sid}', student_sid=sid, ) std_commit(allow_test_environment=True) category = self._api_create_category(client, degree_check.id) subcategory = self._api_create_category( client, degree_check.id, category_type='Subcategory', parent_category_id=category['id'], ) campus_requirements_category = self._api_create_category( client, degree_check.id, category_type='Campus Requirements') campus_requirements = DegreeProgressCategory.find_by_parent_category_id( campus_requirements_category['id']) api_json = _api_get_degree(client, degree_check_id=degree_check.id) course_id = api_json['courses']['unassigned'][-1]['id'] # Expect failure _api_assign_course(category_id=category['id'], client=client, course_id=course_id, expected_status_code=400) _api_assign_course(category_id=campus_requirements_category['id'], client=client, course_id=course_id, expected_status_code=400) _api_assign_course(category_id=campus_requirements[0].id, client=client, course_id=course_id, expected_status_code=400) # Expect success _api_assign_course(category_id=subcategory['id'], client=client, course_id=course_id)
def mock_degree_course(): marker = datetime.now().timestamp() sid = '11667051' degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), degree_name=f'Degree check of {coe_student_sid}', student_sid=sid, ) return DegreeProgressCourse.create( degree_check_id=degree_check.id, display_name=f'The Decline of Western Civilization ({marker})', grade='B+', section_id=datetime.utcfromtimestamp(0).microsecond, sid=sid, term_id=2218, units=4, )
def test_unassigned_courses(self, client, fake_auth): """Authorized user can un-assign a course.""" advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(advisor.uid) # Set up sid = '11667051' degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=advisor.id, degree_name=f'Degree check for {sid}', student_sid=sid, ) category = DegreeProgressCategory.create( category_type='Category', name=f'Category for {sid}', position=1, template_id=degree_check.id, ) std_commit(allow_test_environment=True) # Fetch api_json = _api_get_degree(client, degree_check_id=degree_check.id) assigned_courses = api_json['courses']['assigned'] unassigned_courses = api_json['courses']['unassigned'] assert len(unassigned_courses) assigned_course_count = len(assigned_courses) unassigned_course_count = len(unassigned_courses) # Assign unassigned_course = unassigned_courses[-1] _api_assign_course(client=client, category_id=category.id, course_id=unassigned_course['id']) api_json = _api_get_degree(client, degree_check_id=degree_check.id) # Verify assigned_courses = api_json['courses']['assigned'] assert len(assigned_courses) == assigned_course_count + 1 assert next((c for c in unassigned_courses if c['sectionId'] == unassigned_course['sectionId']), None) unassigned_courses = api_json['courses']['unassigned'] assert len(unassigned_courses) == unassigned_course_count - 1 assert next((c for c in unassigned_courses if c['sectionId'] == unassigned_course['sectionId']), None) is None
def test_ignored_courses(self, client, fake_auth): """Authorized user can ignore a course.""" advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(advisor.uid) # Set up sid = '11667051' degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=advisor.id, degree_name=f'Degree check for {sid}', student_sid=sid, ) std_commit(allow_test_environment=True) # Fetch api_json = _api_get_degree(client, degree_check_id=degree_check.id) unassigned_courses = api_json['courses']['unassigned'] ignored_courses = api_json['courses']['ignored'] ignored_courses_count = len(ignored_courses) # Ignore ignore_this_course = unassigned_courses[-1] _api_assign_course( client=client, category_id=None, course_id=ignore_this_course['id'], ignore=True, ) api_json = _api_get_degree(client, degree_check_id=degree_check.id) # Verify unassigned_courses = api_json['courses']['unassigned'] assert next((c for c in unassigned_courses if c['sectionId'] == ignore_this_course['sectionId']), None) is None ignored_courses = api_json['courses']['ignored'] assert len(ignored_courses) == ignored_courses_count + 1 assert next((c for c in ignored_courses if c['sectionId'] == ignore_this_course['sectionId']), None)
def test_assign_and_unassign_course(self, app, client, fake_auth): """User can assign and unassign a course.""" advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(advisor.uid) sid = '11667051' # Set up degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=advisor.id, degree_name=f'Degree check for {sid}', student_sid=sid, ) original_updated_at = degree_check.updated_at category = DegreeProgressCategory.create( category_type='Category', name=f'Category for {sid}', position=1, template_id=degree_check.id, ) std_commit(allow_test_environment=True) # Assign api_json = _api_get_degree(client, degree_check_id=degree_check.id) course_id = api_json['courses']['unassigned'][-1]['id'] course = _api_assign_course(category_id=category.id, client=client, course_id=course_id) original_grade = course['grade'] assert original_grade == 'P' # Verify assignment api_json = _api_get_degree(client, degree_check_id=degree_check.id) assert course['categoryId'] == category.id assert course_id in [c['id'] for c in api_json['courses']['assigned']] assert course_id not in [ c['id'] for c in api_json['courses']['unassigned'] ] # Modify grade in data lake and verify that assigned course receives the update modified_grade = 'C+' _change_grade_in_data_loch( app=app, grade=modified_grade, section_id=course['sectionId'], sid=sid, term_id=course['termId'], ) api_json = _api_get_degree(client, degree_check_id=degree_check.id) assigned_course = api_json['courses']['assigned'][0] assert assigned_course['id'] == course_id assert assigned_course['grade'] == modified_grade _change_grade_in_data_loch( app=app, grade=original_grade, section_id=course['sectionId'], sid=sid, term_id=course['termId'], ) # Unassign _api_assign_course(category_id=None, client=client, course_id=course_id) api_json = _api_get_degree(client, degree_check_id=degree_check.id) assert course_id not in [ c['id'] for c in api_json['courses']['assigned'] ] unassigned_course = api_json['courses']['unassigned'][-1] assert unassigned_course['id'] == course_id assert unassigned_course['grade'] == original_grade # Verify update of updated_at assert DegreeProgressTemplate.find_by_id( degree_check.id).updated_at != original_updated_at
def mock_template(): return DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=AuthorizedUser.get_id_per_uid(coe_advisor_read_write_uid), degree_name='Zoology BS 2021', )
def test_delete(self, client, fake_auth): """Advisor can delete course.""" advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(advisor.uid) sid = '11667051' degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=advisor.id, degree_name=f'Degree for {sid}', student_sid=sid, ) api_json = _api_get_degree(client, degree_check_id=degree_check.id) course_id = api_json['courses']['unassigned'][-1]['id'] categories = [] for index in (1, 2, 3): categories.append( self._api_create_category( category_type='Category', client=client, name=f'Category {index}', position=index, template_id=degree_check.id, ), ) # Category #2 gets a child 'Course Requirement' course_requirement = self._api_create_category( category_type='Course Requirement', client=client, name='Course Requirement', parent_category_id=categories[1]['id'], position=categories[1]['position'], template_id=degree_check.id, ) # Assign course to Category #1 course = _api_assign_course( category_id=categories[0]['id'], client=client, course_id=course_id, ) # Copy course to Category #2 and then assign it to the 'Course Requirement' course_id = course['id'] course_copy_1 = _api_copy_course( category_id=categories[1]['id'], client=client, course_id=course_id, ) placeholder_category_id = course_copy_1['categoryId'] course_copy_1 = _api_assign_course( category_id=course_requirement['id'], client=client, course_id=course_copy_1['id'], ) # Placeholder category is auto-deleted during re-assignment assert not DegreeProgressCategory.find_by_id(placeholder_category_id) # Delete the course_copy_1 and expect the underlying 'Course Requirement' to survive assert client.delete( f"/api/degree/course/{course_copy_1['id']}").status_code == 200 # Copy course to Category #3 and then delete. Expect removal of course and its 'Placeholder' category. course_copy_2 = _api_copy_course( category_id=categories[2]['id'], client=client, course_id=course_id, ) placeholder_category_id = course_copy_2['categoryId'] assert 'Placeholder' in DegreeProgressCategory.find_by_id( placeholder_category_id).category_type assert client.delete( f"/api/degree/course/{course_copy_2['id']}").status_code == 200 assert not DegreeProgressCategory.find_by_id(placeholder_category_id)
def test_copy_course(self, client, fake_auth): """User can copy course and add it to a category.""" advisor = AuthorizedUser.find_by_uid(coe_advisor_read_write_uid) fake_auth.login(advisor.uid) sid = '11667051' degree_check = DegreeProgressTemplate.create( advisor_dept_codes=['COENG'], created_by=advisor.id, degree_name=f'Degree for {sid}', student_sid=sid, ) std_commit(allow_test_environment=True) degree_check_id = degree_check.id # Set up def _create_category(category_type='Category', parent_category_id=None): category = DegreeProgressCategory.create( category_type=category_type, name= f'{category_type} for {sid} ({datetime.now().timestamp()})', parent_category_id=parent_category_id, position=1, template_id=degree_check_id, ) return category category_1 = _create_category() category_2 = _create_category() std_commit(allow_test_environment=True) # Get sample course from list of unassigned courses api_json = _api_get_degree(client=client, degree_check_id=degree_check_id) course = api_json['courses']['unassigned'][-1] course_id = course['id'] section_id = course['sectionId'] copied_course_ids = [] def _copy_course(category_id, expected_status_code=200): course_copy = _api_copy_course( category_id=category_id, client=client, course_id=course_id, expected_status_code=expected_status_code, ) if expected_status_code == 200: copied_course_ids.append(course_copy['id']) return course_copy # Verify: user cannot copy an unassigned course. _copy_course(category_id=category_1.id, expected_status_code=400) # Verify: user cannot copy course to a category which already has the course. _api_assign_course(category_id=category_1.id, client=client, course_id=course_id) by_id = DegreeProgressCourse.find_by_id(course_id) assert by_id.category_id == category_1.id _copy_course(category_id=category_1.id, expected_status_code=400) subcategory = _create_category(category_type='Subcategory', parent_category_id=category_1.id) # Verify: user cannot copy course to a category which has subcategories. _copy_course(category_id=category_1.id, expected_status_code=400) # Verify we can create a copy of course in subcategory, thereby provisioning a new 'Course Requirement' child_count = len( DegreeProgressCategory.find_by_parent_category_id(subcategory.id)) copy_of_course = _copy_course(category_id=subcategory.id) children = DegreeProgressCategory.find_by_parent_category_id( subcategory.id) assert len(children) == child_count + 1 assert children[0].category_type == 'Placeholder: Course Copy' assert copy_of_course['categoryId'] == children[0].id # Assign the copied course to an actual Course Requirement and verify that "placeholder" category is deleted. course_requirement = _create_category( category_type='Course Requirement', parent_category_id=subcategory.id) placeholder_category_id = copy_of_course['categoryId'] _api_assign_course(category_id=course_requirement.id, client=client, course_id=copy_of_course['id']) assert DegreeProgressCategory.find_by_id( placeholder_category_id) is None # Finally, we create a copy for a separate category and expect success. copy_of_course = _copy_course(category_id=category_2.id) assert copy_of_course['id'] != course_id assert copy_of_course['sectionId'] == section_id # Verify 'isCopy' property per course degree_json = _api_get_degree(client=client, degree_check_id=degree_check_id) assigned_courses = degree_json['courses']['assigned'] unassigned_courses = degree_json['courses']['unassigned'] assert len(assigned_courses) assert len(unassigned_courses) # Expect no "copies" in the Unassigned set of courses. assert True not in [c['isCopy'] for c in unassigned_courses] for assigned_course in assigned_courses: course_id = assigned_course['id'] assert assigned_course['isCopy'] == (course_id in copied_course_ids)