def test_select_student(self, monkeypatch, selected_student_name, selected_student_students_index): test_class_students = [ Student(name='one'), Student(name='two'), Student(name='three') ] test_class = Class(name='some_class', students=test_class_students) test_student_options = {1: 'one', 2: 'two', 3: 'three'} def mocked_display_student_selection_menu(class_options): if class_options != test_student_options: raise ValueError # Ensure called with correct arg. return None def mocked_take_student_selection(class_options): if class_options != test_student_options: raise ValueError # Ensure called with correct arg. return selected_student_name monkeypatch.setattr(class_functions, 'display_student_selection_menu', mocked_display_student_selection_menu) monkeypatch.setattr(class_functions, 'take_student_selection', mocked_take_student_selection) assert select_student( test_class) == test_class_students[selected_student_students_index]
def test_create_chart(self, request, database_backend): """ Verify API works. NB No verification in API test, as API for verifying does not exist. TODO: Verify saved chart contents when load/edit features added. """ test_database = request.getfixturevalue(database_backend) test_class = NewClass(name='test_class', students=[ Student(name='bad student'), Student(name='mediocre student'), Student(name='excellent student'), ]) test_database.create_class(test_class) test_chart_data_dict = { 'class_id': test_class.id, 'class_name': test_class.name, 'chart_name': 'test_chart_name', 'chart_default_filename': 'test_default_chart_filename', 'chart_params': { 'some': 'params' }, 'score-students_dict': { 0: [test_class.students[0]], 50: [test_class.students[1]], 100: [test_class.students[2]], } } assert test_database.create_chart(test_chart_data_dict) is None
class TestClassStr: @pytest.mark.parametrize( 'class_object,' 'expected_str', [ (Class(name='name_only_class'), f"Class {'name_only_class'}, with id=None, containing 0 students." ), (Class(class_id='some class id', name='name_only_class with id'), f"Class {'name_only_class with id'}, with id={'some class id'}, containing 0 students." ), (Class.from_dict(test_full_class_data_set['json_dict_rep']), f"Class {Class.from_dict(test_full_class_data_set['json_dict_rep']).name}, " f"with id={Class.from_dict(test_full_class_data_set['json_dict_rep']).id}, " f"containing {len(Class.from_dict(test_full_class_data_set['json_dict_rep']).students)} students, " f"with names: {', '.join([student.name for student in Class.from_dict(test_full_class_data_set['json_dict_rep']).students])}." ), (Class(class_id='some class id', name='small_class with id', students=[Student(name='one'), Student(name='two') ]), f"Class {'small_class with id'}, " f"with id={'some class id'}, " f"containing 2 students, " f"with names: one, two."), ]) def test_str(self, class_object, expected_str): assert str(class_object) == expected_str
class TestStudentRepr: @pytest.mark.parametrize('student_object', [Student(name='I have no avatar!'), Student(name='I have an avatar', avatar_id='my_avatar_id.png'), ]) def test_repr(self, student_object): assert repr(student_object) == (f'{student_object.__class__.__module__}' f'.{student_object.__class__.__name__}(' f'id={student_object.id if student_object.id else None !r}, ' f'name={student_object._name!r}, ' f'avatar_id={student_object._avatar_id!r})')
class TestStudentStr: @pytest.mark.parametrize( 'student_object,' 'expected_str', [(Student(name='I have no avatar!'), f"Student {'I have no avatar!'}, with no avatar, and id=None."), (Student(name='I have an avatar', avatar_id='path_to_my_avatar'), f"Student {'I have an avatar'}, with avatar {'path_to_my_avatar'}, and id=None."), (Student(student_id='some id', name='I have an id and avatar', avatar_id='path_to_my_avatar'), f"Student {'I have an id and avatar'}, with avatar {'path_to_my_avatar'}, and id=some id.") ]) def test_str(self, student_object, expected_str, test_student_name_only, test_student_with_avatar): assert str(student_object) == expected_str
class TestStudentJsonDict: @pytest.mark.parametrize( 'student_object,output_json', [(Student('Sir Galahad'), {'name': 'Sir Galahad'}), # name only (Student('Sir Lancelot: the Brave', avatar_id=None), {'name': 'Sir Lancelot: the Brave'}), (Student('Arther, King of the Britons', avatar_id='Holy_Grail.jpg'), {'name': 'Arther, King of the Britons', 'avatar_id': 'Holy_Grail.jpg'} ), (Student('Brian', avatar_id='a_naughty_boy.png'), {'name': 'Brian', 'avatar_id': 'a_naughty_boy.png'}), ]) def test_json_dict(self, student_object, output_json): assert student_object.json_dict() == output_json
def test_score_table_repr(self, empty_sqlite_sqlalchemy_database): test_database = empty_sqlite_sqlalchemy_database test_class = NewClass(name='test_class', students=[ Student(name='bad student'), Student(name='mediocre student'), Student(name='excellent student'), Student(name='another mediocre student'), ]) test_database.create_class(test_class) test_chart_data_dict = { 'class_id': test_class.id, 'class_name': test_class.name, 'chart_name': 'test_chart_name', 'chart_default_filename': 'test_default_chart_filename', 'chart_params': { 'some': 'params' }, 'score-students_dict': { 0: [test_class.students[0]], 50.0: [test_class.students[1], test_class.students[3]], 100: [test_class.students[2]], } } test_database.create_chart(test_chart_data_dict) scores_data = [] score_id = 1 # initiate score id for score, students in test_chart_data_dict[ 'score-students_dict'].items(): # NB One chart in db -> chart.id = 1] for student in students: scores_data += [(score_id, test_chart_data_dict['chart_id'], student.id, score)] score_id += 1 # Verify reprs of scores in db: score_strings = [ ( f"<Score(" f"id={score[0]}, " f"chart_id={score[1]}, " f"student_id={score[2]}, " f"value={float(score[3])}" # Value is stored as a float. f")>") for score in scores_data ] with test_database.session_scope() as test_session: assert repr(test_session.query( test_database.Score).all()) == f"[{', '.join(score_strings)}]"
def test_create_chart(self, empty_sqlite_sqlalchemy_database): """ Verify API works. NB No verification in API test, as API for verifying does not exist. TODO: Verify saved chart contents when load/edit features added. """ test_database = empty_sqlite_sqlalchemy_database test_class = NewClass(name='test_class', students=[ Student(name='bad student'), Student(name='mediocre student'), Student(name='excellent student'), Student(name='another mediocre student'), ]) test_database.create_class(test_class) test_chart_data_dict = { 'class_id': test_class.id, 'class_name': test_class.name, 'chart_name': 'test_chart_name', 'chart_default_filename': 'test_default_chart_filename', 'chart_params': { 'some': 'params' }, 'score-students_dict': { 0: [test_class.students[0]], 50: [test_class.students[1], test_class.students[3]], 100: [test_class.students[2]], } } assert test_database.create_chart(test_chart_data_dict) is None # Verify chart data in db: # A load_chart method might go here. with test_database.session_scope() as test_session: assert test_session.execute( """SELECT chart.name FROM chart""").fetchone( )[0] == test_chart_data_dict['chart_name'] scores_data = [] for score, students in test_chart_data_dict[ 'score-students_dict'].items(): # NB One chart in db -> chart.id = 1 scores_data += [(1, student.id, score) for student in students] assert test_session.execute( """SELECT chart_id, student_id, value FROM score""").fetchall( ) == scores_data # Ensure chart id added to chart_data_dict: assert test_chart_data_dict['chart_id'] == 1
class TestStudentStr: @pytest.mark.parametrize( 'student_object,' 'expected_str', [ (Student(name='I have no avatar!'), f"Student {'I have no avatar!'}, with no avatar."), (Student(name='I have an avatar', avatar_filename='path_to_my_avatar'), f"Student {'I have an avatar'}, with avatar {'path_to_my_avatar'}." ), ]) def test_str(self, student_object, expected_str, test_student_name_only, test_student_with_avatar): assert str(student_object) == expected_str
class TestStudentName: """ Test Student name and path_safe_name properties. """ def test_name_getter(self): test_name = 'Arthur, King of the Britons' assert Student(name='Arthur, King of the Britons').name == test_name def test_name_setter_unmocked(self, test_student_name_only): test_changed_name = 'Sir Lancelot: the not-so-brave' assert test_student_name_only.name != test_changed_name # Change name test_student_name_only.name = test_changed_name assert test_student_name_only.name == test_changed_name @pytest.mark.parametrize( 'name_arg', [{'passing a dict': 'Some value'}, # dict ['passing', 'a', 'list'], # list Student('Student object for name'), # Student object ('passed', 'tuple',), # tuple ]) def test_non_str_name_raises_error(self, name_arg): """Test error is raised for each bad type, error msg contains type.""" with pytest.raises(TypeError, match=str(type(name_arg))): Student(name=name_arg)
def test_instantiate_with_avatar_id(self): test_instantiate_student_with_avatar = Student(self.test_name, avatar_id=self.test_avatar_id) assert test_instantiate_student_with_avatar.avatar_id == self.test_avatar_id assert isinstance(test_instantiate_student_with_avatar.avatar_id, str)
def load_class(self, class_id: Any) -> Class: """ Load class from database using primary key class.id. :param class_id: :return: Class """ with self.session_scope() as session: class_data = session.query(self.Class, self.Student).filter( self.Class.id == self.Student.class_id).filter( self.Student.class_id == class_id).all() if class_data: class_id, class_name = class_data[0][0].id, class_data[0][ 0].name students_list = [ Student( student_id=student.id, name=student.name, class_id=class_data.id, avatar_id=student.avatar_id, ) for class_data, student in class_data ] else: # Empty class empty_class = session.query( self.Class).filter(self.Class.id == class_id).one() students_list = [] class_id, class_name = empty_class.id, empty_class.name return Class(class_id=class_id, name=class_name, students=students_list)
def test_update_class(self, empty_json_database, test_full_class): test_json_database = empty_json_database test_class = Class(test_full_class.name, test_full_class.students) # create_class takes a NewClass object due to avatar moving machinery. test_class_new_class = NewClass(test_full_class.name, test_full_class.students) assert test_class.json_dict() == test_class_new_class.json_dict( ) # Ensure classes are the same. # Create class in database. test_json_database.create_class( NewClass(test_full_class.name, test_full_class.students)) # Ensure test_class in database test_class.id = test_class.name # Loaded class will have ids: test_loaded_class = test_full_class.json_dict() for student in test_loaded_class['students']: student['id'] = student['name'] assert test_json_database.load_class( test_class.name).json_dict() == test_loaded_class # Change class by adding student, update database: new_student = Student(name='new student') assert new_student not in test_class and new_student.name not in test_class # Confirm student not in class. test_class.add_student(new_student) assert test_json_database.update_class(test_class) is None # Look up name because new_student object itself is not in the loaded class object. assert new_student.name in test_json_database.load_class( test_class.name)
def test_take_class_data_input(self, monkeypatch): test_class_name = 'my test class' test_take_student_name_input_returns = ['test_student', 'END'] take_student_avatar_return = 'my_student_avatar.jpg' test_class = Class( name=test_class_name, students=[ Student(name=test_take_student_name_input_returns[0], avatar_id=take_student_avatar_return), ]) test_take_student_name_input_return = ( name for name in test_take_student_name_input_returns) def mocked_take_student_name_input(new_class): if new_class.name != test_class_name: raise ValueError return next(test_take_student_name_input_return) def mocked_take_student_avatar(new_class, student_name): if (new_class.name, student_name) != ( test_class_name, test_take_student_name_input_returns[0]): raise ValueError return take_student_avatar_return monkeypatch.setattr(class_functions, 'take_student_avatar', mocked_take_student_avatar) monkeypatch.setattr(class_functions, 'take_student_name_input', mocked_take_student_name_input) assert take_class_data_input( test_class_name).json_dict() == test_class.json_dict()
def setUp(self): self.no_student_name = '' self.blank_student_name = '_' self.preexisting_student_name = 'this student already exists in the class' self.valid_new_student_name = 'this is a valid student_name' self.invalid_student_name_response = 'Please enter a valid student name.' self.preexisting_student_response = 'This student is already a member of the class.' self.test_case_inputs = [ self.no_student_name, self.blank_student_name, self.preexisting_student_name, self.valid_new_student_name, ] self.printed_feedback = [ self.invalid_student_name_response, self.invalid_student_name_response, self.preexisting_student_response, ] self.test_class = Class( name='my_test_class', students=[Student(name=self.preexisting_student_name)])
def test_class_table_repr(self, empty_sqlite_sqlalchemy_database): test_database = empty_sqlite_sqlalchemy_database test_class = NewClass(name='test_class', students=[ Student(name='bad student'), Student(name='mediocre student'), Student(name='excellent student'), Student(name='another mediocre student'), ]) test_database.create_class(test_class) # Verify repr of (only) class in db: with test_database.session_scope() as test_session: assert repr(test_session.query(test_database.Class).one( )) == f"<Class(id={test_class.id}, name={test_class.name})>"
def test_get_avatar_path(self, tmpdir, request, database_backend, avatar_provided): """""" test_database = request.getfixturevalue(database_backend) # Create avatar: test_avatar_data = b'some binary data' test_avatar_path = Path(tmpdir, 'test_avatar.png') test_avatar_path.write_bytes(test_avatar_data) # Add avatar to db: test_class = NewClass( name='test class', students=[ Student( name='test_student', avatar_id=(test_avatar_path if avatar_provided else None), ) ]) test_database.create_class(test_class) # Find avatar_id, load class to verify: classes = test_database.get_classes() test_class_id = classes[0].id # As the only class will be first item. loaded_test_class = test_database.load_class(test_class_id) test_avatar_id = loaded_test_class.students[0].avatar_id # Path may be different/random - test data: assert test_database.get_avatar_path(test_avatar_id).read_bytes() == ( test_avatar_data if avatar_provided else test_database.default_avatar_path.read_bytes())
def test_instantiate_with_avatar_filename(self): test_instantiate_student_with_avatar = Student( self.test_name, avatar_filename=self.test_avatar_filename) assert test_instantiate_student_with_avatar.avatar_filename == self.test_avatar_filename assert isinstance(test_instantiate_student_with_avatar.avatar_filename, str)
def test_chart_table_repr(self, empty_sqlite_sqlalchemy_database): test_database = empty_sqlite_sqlalchemy_database test_class = NewClass(name='test_class', students=[ Student(name='bad student'), Student(name='mediocre student'), Student(name='excellent student'), Student(name='another mediocre student'), ]) test_database.create_class(test_class) test_chart_data_dict = { 'class_id': test_class.id, 'class_name': test_class.name, 'chart_name': 'test_chart_name', 'chart_default_filename': 'test_default_chart_filename', 'chart_params': { 'some': 'params' }, 'score-students_dict': { 0: [test_class.students[0]], 50: [test_class.students[1], test_class.students[3]], 100: [test_class.students[2]], } } test_database.create_chart(test_chart_data_dict) # Create fake plot/image mock_plt = plt.figure(figsize=(19.20, 10.80)) test_image = io.BytesIO() mock_plt.savefig(test_image, format='png', dpi=300) test_image.seek(0) # Return pointer to start of binary stream. test_database.save_chart_image(test_chart_data_dict, mock_plt) # Verify repr of (only) chart in db: with test_database.session_scope() as test_session: assert repr(test_session.query(test_database.Chart).one()) == ( f"<Chart(" f"id={test_chart_data_dict['chart_id']}, " f"name={test_chart_data_dict['chart_name']}, " f"image={test_image.read1()}, " f"date=None" # Not implemented yet. f")>")
def transform_data(class_name: str, old_class_data: dict): """ Take class name (eg from old style cld filename), and loaded json dict, transform into a new-style Class object. :param class_name: str :param old_class_data: dict :return: Class object """ new_students = [] for student_name in old_class_data: if old_class_data[student_name][0] is None: new_students.append(Student(name=student_name)) else: new_students.append( Student(name=student_name, avatar_filename=old_class_data[student_name][0]), ) new_class = Class(name=class_name, students=new_students) return new_class
def test_store_students_as_student_names(self, empty_json_database): test_json_database = empty_json_database test_json_database.default_avatar_path = Path('default/path') test_class = NewClass(name='test_class', students=[ Student(name='bad student'), Student(name='mediocre student'), Student(name='excellent student'), ]) test_data_dict = { 'class_name': "test_class_name", 'chart_name': "test_chart_name", 'chart_default_filename': "test_chart_default_filename", 'chart_params': { "some": "chart", "default": "params" }, 'score-students_dict': { 0: [test_class.students[0]], 50: [test_class.students[1]], 100: [test_class.students[2]], }, } test_returned_sanitised_data_dict = { 'class_name': "test_class_name", 'chart_name': "test_chart_name", 'chart_default_filename': "test_chart_default_filename", 'chart_params': { "some": "chart", "default": "params" }, 'score-students_dict': { 0: [test_class.students[0].name], 50: [test_class.students[1].name], 100: [test_class.students[2].name], }, } assert test_json_database._store_students_as_student_names( test_data_dict) == test_returned_sanitised_data_dict
def setUp(self) -> None: self.test_class_name = 'my test class' self.test_student_name_input_returns = ['test_student', 'END'] self.take_student_avatar_return = 'my_student_avatar.jpg' self.test_class = Class( name=self.test_class_name, students=[ Student(name=self.test_student_name_input_returns[0], avatar_filename=self.take_student_avatar_return), ])
def test_from_dict(self, output_json_dict): student_object = Student.from_dict(output_json_dict) assert isinstance(student_object, Student) # Verify instantiated object is equivalent by reserialising: assert student_object.json_dict() == output_json_dict # Test attributes assert student_object.name == output_json_dict['name'] if output_json_dict.get('avatar_filename') is not None: assert str(student_object.avatar_filename ) == output_json_dict['avatar_filename']
def test_full_class() -> Class: test_full_class = Class( class_id=test_full_class_data_set['json_dict_rep']['id'], name=test_full_class_data_set['json_dict_rep']['name']) for student in test_full_class_data_set['json_dict_rep']['students']: test_full_class.add_student(Student(**student)) test_full_class.json_str_rep = test_full_class_data_set['json_str_rep'] test_full_class.json_dict_rep = test_full_class_data_set['json_dict_rep'] return test_full_class
def test_full_new_class(): """Returns empty NewClass instantiated with students.""" test_full_new_class = NewClass( test_full_class_data_set['json_dict_rep']['name']) for student in test_full_class_data_set['json_dict_rep']['students']: test_full_new_class.add_student(Student(**student)) test_full_new_class.json_str_rep = test_full_class_data_set['json_str_rep'] test_full_new_class.json_dict_rep = test_full_class_data_set[ 'json_dict_rep'] return test_full_new_class
def from_dict(cls, class_dict: dict): """ Instantiate Class object from JSON-serialisable dict. :param class_dict: dict :return: Class object """ _name = class_dict['name'] _students = [ Student.from_dict(student) for student in class_dict['students'] ] return Class(_name, _students)
def test_student_table_repr(self, empty_sqlite_sqlalchemy_database): test_database = empty_sqlite_sqlalchemy_database test_class = NewClass(name='test_class', students=[ Student(name='bad student'), Student(name='mediocre student'), Student(name='excellent student'), Student(name='another mediocre student'), ]) test_database.create_class(test_class) # Verify reprs of students in db: student_strings = [(f"<Student(" f"id={student.id}, " f"name={student.name}, " f"class_id={test_class.id}, " f"avatar_id={student.avatar_id}" f")>") for student in test_class.students] with test_database.session_scope() as test_session: assert repr(test_session.query(test_database.Student).all() ) == f"[{', '.join(student_strings)}]"
def test__contains__identical_but_not_actual_student_obj_in_class( self, test_student_name_only, test_class_name_only): """ Test identical student object in class. Should return False, since the specific object is not in the class. eg id(test_student) != id(test_class_name_only.students[0]) """ assert test_class_name_only.students == [] # No students in class name, avatar_id = 'test_student', 'test_student_avatar' test_student = Student(name=name, avatar_id=avatar_id) test_class_name_only.add_student(name=name, avatar_id=avatar_id) assert test_student not in test_class_name_only
def from_dict(cls, class_dict: dict) -> Union['Class', 'NewClass']: """ Instantiate Class object from JSON-serialisable dict. :param class_dict: dict :return: Class object """ _id = class_dict.get('id') # Class may not have id if not in db. _name = class_dict['name'] _students = [ Student.from_dict(student) for student in class_dict['students'] ] return cls(class_id=_id, name=_name, students=_students)
def test_save_chart_image(self, tmpdir, empty_sqlite_sqlalchemy_database): """Save image and verify data.""" test_database = empty_sqlite_sqlalchemy_database test_data_dict = { 'class_id': 314, 'class_name': "test_class_name", 'chart_name': "test_chart_name", 'chart_default_filename': "test_chart_default_filename", 'chart_params': { "some": "chart", "default": "params" }, 'score-students_dict': { 0: [Student(student_id=42, name="Brian")], }, } # Create chart in db: test_database.create_chart(test_data_dict) # Create fake plot/image mock_plt = plt.figure(figsize=(19.20, 10.80)) test_image = io.BytesIO() mock_plt.savefig(test_image, format='png', dpi=300) test_image.seek(0) # Return pointer to start of binary stream. save_chart_path = test_database.save_chart_image( test_data_dict, mock_plt) # Path exists and image at path/db is expected data: assert save_chart_path.exists() assert save_chart_path.read_bytes() == test_image.read1( ) # size arg can be omitted on 3.7+ test_image.seek( 0) # Return pointer to start of test_image binary stream. # Compare db image with test_database.session_scope() as test_session: db_image = test_session.query(test_database.Chart).filter_by( id=test_data_dict['chart_id']).first().image # Images must both be saved as '.png' for comparison. test_image_path = Path(tmpdir, 'test_image.png') test_image_path.write_bytes(test_image.read1()) test_image.seek(0) db_image_path = Path(tmpdir, 'db_image.png') db_image_path.write_bytes(db_image) try: assert not compare_images( db_image_path, test_image_path, 0.0001) # Returns str on fail, None on success. except MemoryError: pass # fails for 32 bit python on Windows. assert save_chart_path.read_bytes() == test_image.read()