diff --git a/bbcli/commands/assignments.py b/bbcli/commands/assignments.py index 7421099600524c16916a5b3ac1578c2d508ba774..5d3a6587741b54ba31ed6d1ae87206eb9b47007a 100644 --- a/bbcli/commands/assignments.py +++ b/bbcli/commands/assignments.py @@ -54,8 +54,8 @@ def create_assignment(ctx, course_id: str, parent_id: str, title: str, @click.pass_context @list_exception_handler def get_assignments(ctx, course_id): - assignment_service.get_assignments(ctx.obj['SESSION'], course_id) - + response = assignment_service.get_assignments(ctx.obj['SESSION'], course_id) + assignment_service.print_assignments(response) @click.command(name='list', help='List attempts for an assignment.') @click.option('-c', '--course', 'course_id', required=True, help='COURSE ID, of the course you want the assignment attempts from') @@ -64,8 +64,11 @@ def get_assignments(ctx, course_id): @click.pass_context @list_exception_handler def get_attempts(ctx, course_id, column_id, submitted): - assignment_service.get_column_attempts( - ctx.obj['SESSION'], course_id, column_id, print_submitted=submitted) + response = assignment_service.get_column_attempts(ctx.obj['SESSION'], course_id, column_id) + if submitted: + assignment_service.print_submitted_attempts(response) + else: + assignment_service.print_all_attempts(response) # TODO: Retrieve the submission w/ attachments. @@ -76,23 +79,22 @@ def get_attempts(ctx, course_id, column_id, submitted): @click.pass_context @list_exception_handler def get_attempt(ctx, course_id, column_id, attempt_id): - assignment_service.get_column_attempt( - ctx.obj['SESSION'], course_id, column_id, attempt_id) - + response = assignment_service.get_column_attempt(ctx.obj['SESSION'], course_id, column_id, attempt_id) + click.echo(response) @click.command(name='submit', help='Submit assignment attempt.') @click.option('-c', '--course', 'course_id', required=True, help='COURSE ID, of the course to submit an assignment to.') @click.option('-a', '--assignment', 'column_id', required=True, help='ASSIGNMENT ID, of the assignment you want to submit to.') -@click.option('--studentComments', help='The student comments associated with this attempt.') -@click.option('--studentSubmission', help='The student submission text associated with this attempt.') +@click.option('--student-comments', help='The student comments associated with this attempt.') +@click.option('--student-submission', help='The student submission text associated with this attempt.') @click.option('--file', help='Attach a file to an attempt for a Student Submission. Relative path of file.') @click.option('--draft', is_flag=True) @click.pass_context @create_exception_handler -def submit_attempt(ctx, course_id, column_id, studentComments, studentSubmission, file, draft): - assignment_service.create_column_attempt( - ctx.obj['SESSION'], course_id, column_id, studentComments=studentComments, studentSubmission=studentSubmission, dst=file, status='needsGrading', draft=draft) - +def submit_attempt(ctx, course_id, column_id, student_comments, student_submission, file, draft): + response = assignment_service.create_column_attempt( + ctx.obj['SESSION'], course_id, column_id, studentComments=student_comments, studentSubmission=student_submission, dst=file, status='needsGrading', draft=draft) + click.echo(response) @click.command(name='submit-draft', help='Submit assignment draft.') @click.option('-c', '--course', 'course_id', required=True, help='COURSE ID, of the course where the assignment is.') @@ -101,9 +103,9 @@ def submit_attempt(ctx, course_id, column_id, studentComments, studentSubmission @click.pass_context @update_exception_handler def submit_draft(ctx, course_id, column_id, attempt_id): - assignment_service.update_column_attempt( + response = assignment_service.update_column_attempt( ctx.obj['SESSION'], course_id=course_id, column_id=column_id, attempt_id=attempt_id, status='needsGrading') - + click.echo(response) @click.command(name='update', help='Update assignment.') @click.option('-c', '--course', 'course_id', required=True, help='COURSE ID, of the course where the assignment is.') @@ -115,9 +117,9 @@ def submit_draft(ctx, course_id, column_id, attempt_id): @click.pass_context @update_exception_handler def update_attempt(ctx, course_id, column_id, attempt_id, status, comments, submission, file): - assignment_service.update_column_attempt( + response = assignment_service.update_column_attempt( session=ctx.obj['SESSION'], course_id=course_id, column_id=column_id, attempt_id=attempt_id, status=status, studentComments=comments, studentSubmission=submission, dst=file) - + click.echo(response) @click.command(name='grade', help='Grade an assignment.') @click.option('-c', '--course', 'course_id', required=True, help='COURSE ID, of the course where the assignment is.') @@ -130,5 +132,6 @@ def grade_assignment(ctx, course_id, column_id, attempt_id, status, score, text, if status is None: status = 'Completed' - assignment_service.update_column_attempt(session=ctx.obj['SESSION'], status=status, course_id=course_id, column_id=column_id, + response = assignment_service.update_column_attempt(session=ctx.obj['SESSION'], status=status, course_id=course_id, column_id=column_id, attempt_id=attempt_id, score=score, text=text, notes=notes, feedback=feedback, exempt=exempt) + click.echo(response) \ No newline at end of file diff --git a/bbcli/services/assignment_service.py b/bbcli/services/assignment_service.py index 74d514f01a7bc6c2cdf68469907d5fa84248f300..cd9afd82ca16a60d8b41c479760b717eb1fc1cdb 100644 --- a/bbcli/services/assignment_service.py +++ b/bbcli/services/assignment_service.py @@ -19,7 +19,7 @@ def get_assignments(session: requests.Session, course_id): response.raise_for_status() response = json.loads(response.text) results = response['results'] - print_assignments(results) + return results # TODO: This should be in view def print_assignments(assignments): @@ -42,7 +42,7 @@ def utc_to_local(utc_dt): return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None) -def get_column_attempts(session: requests.Session, course_id, column_id, print_submitted): +def get_column_attempts(session: requests.Session, course_id, column_id): url = url_builder.base_v2().add_courses().add_id(course_id).add_gradebook( ).add_columns().add_id(column_id).add_attempts().create() @@ -50,11 +50,7 @@ def get_column_attempts(session: requests.Session, course_id, column_id, print_s response.raise_for_status() response = json.loads(response.text) results = response['results'] - - if print_submitted: - print_submitted_attempts(results) - else: - print_all_attempts(results) + return results def print_submitted_attempts(attempts): @@ -93,7 +89,7 @@ def get_column_attempt(session: requests.Session, course_id, column_id, attempt_ response = session.get(url) attempt = json.loads(response.text) attempt = json.dumps(attempt, indent=2) - click.echo(attempt) + return attempt def create_column_attempt(session: requests.Session, course_id, column_id, studentComments=None, studentSubmission=None, dst: str = None, status=None, draft: bool = False): @@ -110,7 +106,6 @@ def create_column_attempt(session: requests.Session, course_id, column_id, stude json_data = json.dumps(data, indent=2) response = session.post(url, data=json_data) response_json = json.loads(response.text) - click.echo(response_json) if dst is not None and response.status_code == 201: attempt_id = response_json['id'] @@ -120,6 +115,8 @@ def create_column_attempt(session: requests.Session, course_id, column_id, stude update_column_attempt(session, course_id, column_id, attempt_id, status='NeedsGrading') + return response_json + def update_column_attempt(session: requests.Session, course_id, column_id, attempt_id, status=None, score=None, text=None, notes=None, feedback=None, studentComments=None, studentSubmission=None, exempt=None, dst=None): @@ -134,11 +131,11 @@ def update_column_attempt(session: requests.Session, course_id, column_id, attem response = session.patch(url, data=json_data) response.raise_for_status() response = json.loads(response.text) - click.echo(response) if dst is not None: attach_file(session, course_id, attempt_id, dst) + return response def attach_file(session: requests.Session, course_id, attempt_id, dst: str): url = url_builder.base_v1().add_courses().add_id( diff --git a/tests/test_services/test_assignments_services.py b/tests/test_services/test_assignments_services.py new file mode 100644 index 0000000000000000000000000000000000000000..8aa6ca74ac6d3b0ed3021fc2915c652da013b491 --- /dev/null +++ b/tests/test_services/test_assignments_services.py @@ -0,0 +1,109 @@ +import json +from re import M +from typing import List +from click import Abort, BadParameter +import requests + +from unittest.mock import Mock, patch +from nose.tools import assert_list_equal, assert_equal, raises +from bbcli.entities.content_builder_entitites import DateInterval, FileOptions, GradingOptions, StandardOptions, WeblinkOptions +from bbcli.services.assignment_service import create_column_attempt, get_assignments, get_column_attempt, get_column_attempts, update_column_attempt +from bbcli.services.contents_service import create_assignment, create_document, create_externallink, create_file, create_folder, delete_content, update_content +from tests.test_services.test_announcements_services import UPDATED_TEST_ANNOUNCEMENT + + +TEST_ASSIGNMENTS_LIST = {"results":[{"id":"_275617_1","name":"Totalt","description":"<p>Den uvektede totalen av alle vurderinger for en bruker.</p>","externalGrade":True,"score":{"possible":3757.00000},"availability":{"available":"No"},"grading":{"type":"Calculated","schemaId":"_328072_1"},"gradebookCategoryId":"_654726_1","formula":{"formula":"{ \"running\":\"True\", \"all\":{\"average\":\"False\"}}"},"includeInCalculations":True,"showStatisticsToStudents":False},{"id":"_277393_1","name":"posted from CLI test","created":"2022-02-23T12:15:42.847Z","contentId":"_1666340_1","score":{"possible":100.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_277394_1","name":"posted from CLI test","created":"2022-02-23T12:18:11.087Z","contentId":"_1666343_1","score":{"possible":100.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_280663_1","name":"Test_04042022_1538","created":"2022-04-04T13:49:32.442Z","contentId":"_1696651_1","score":{"possible":100.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","due":"2022-04-05T10:04:00.000Z","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_280808_1","name":"testing assignment","created":"2022-04-06T12:52:02.909Z","contentId":"_1698141_1","score":{"possible":69.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_280809_1","name":"testing assignment 2","created":"2022-04-06T12:52:26.148Z","contentId":"_1698143_1","score":{"possible":123.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_280839_1","name":"Mattias har klamma, True or False?","created":"2022-04-07T10:44:18.700Z","contentId":"_1698594_1","score":{"possible":69.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":0,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_280850_1","name":"Kommer mattias på DT ikveld mon tro?","created":"2022-04-07T11:58:42.794Z","contentId":"_1698666_1","score":{"possible":96.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_280968_1","name":"Ttest assignment hehehe","created":"2022-04-11T09:08:40.603Z","contentId":"_1699529_1","score":{"possible":100.00000},"availability":{"available":"No"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_281043_1","name":"Test assignment","created":"2022-04-18T08:59:50.312Z","contentId":"_1699931_1","score":{"possible":1000.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_281044_1","name":"Test assignment","created":"2022-04-18T09:02:48.606Z","contentId":"_1699933_1","score":{"possible":1000.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"},{"id":"_281045_1","name":"Test assignment","created":"2022-04-18T09:03:43.900Z","contentId":"_1699934_1","score":{"possible":1000.00000},"availability":{"available":"Yes"},"grading":{"type":"Attempts","attemptsAllowed":1,"scoringModel":"Last","schemaId":"_328072_1","anonymousGrading":{"type":"None"}},"gradebookCategoryId":"_654732_1","includeInCalculations":True,"showStatisticsToStudents":False,"scoreProviderHandle":"resource/x-bb-assignment"}]} +TEST_ASSIGNMENT_ATTEMPTS_LIST = {"results":[{"id":"_5330107_1","userId":"_111522_1","status":"Completed","displayGrade":{"scaleType":"Score","score":70.00000},"text":"70.00000","score":70.000000000000000,"feedback":"Jaja ca greit nok","studentComments":"Test comment.","studentSubmission":"<p>Test submission.</p>","exempt":False,"created":"2022-04-05T08:36:18.225Z","attemptDate":"2022-04-05T08:36:18.242Z","modified":"2022-04-06T13:38:15.169Z","attemptReceipt":{"receiptId":"e4473469b3674366a7837b0debb4d93e","submissionDate":"2022-04-05T08:36:18.229Z"}},{"id":"_5334307_1","userId":"_140040_1","status":"Completed","displayGrade":{"scaleType":"Score","score":100.00000},"text":"100.00000","score":100.000000000000000,"notes":"Helt UsERR","feedback":"Gratulerer","studentSubmission":"<p>Hva faen, Mattias ga klamma til Chloe \uD83D\uDE2F</p>","exempt":False,"created":"2022-04-06T07:25:46.278Z","attemptDate":"2022-04-06T07:25:46.313Z","modified":"2022-04-06T11:28:51.489Z","attemptReceipt":{"receiptId":"669bea9237164cd2b3dae5feee41f47c","submissionDate":"2022-04-06T07:25:46.283Z"}},{"id":"_5330216_1","userId":"_140955_1","status":"Completed","displayGrade":{"scaleType":"Score","score":100.00000},"text":"100.00000","score":100.000000000000000,"feedback":"Maggie er IKKE taperbb assignments grade --helpbb assignments grade --help","studentComments":"hva skjer","studentSubmission":"<p>adsfadsfadsf</p>","exempt":False,"created":"2022-04-05T08:54:56.286Z","attemptDate":"2022-04-05T08:54:56.302Z","modified":"2022-04-08T16:17:40.022Z","attemptReceipt":{"receiptId":"095f247c4e91492186b4416e94e20c22","submissionDate":"2022-04-05T08:54:56.290Z"}}]} +TEST_ATTEMPT = { + "id": "_5330107_1", + "userId": "_111522_1", + "status": "Completed", + "displayGrade": { + "scaleType": "Score", + "score": 70.0 + }, + "text": "70.00000", + "score": 70.0, + "feedback": "Jaja ca greit nok", + "studentComments": "Test comment.", + "studentSubmission": "<p>Test submission.</p>", + "exempt": False, + "created": "2022-04-05T08:36:18.225Z", + "attemptDate": "2022-04-05T08:36:18.242Z", + "modified": "2022-04-06T13:38:15.169Z", + "attemptReceipt": { + "receiptId": "e4473469b3674366a7837b0debb4d93e", + "submissionDate": "2022-04-05T08:36:18.229Z" + } +} +TEST_SUBMITTED_ATTEMPT = {'id': '_5361991_1', 'userId': '_140040_1', 'status': 'NeedsGrading', 'studentComments': 'I think yes', 'studentSubmission': 'TRUE, Mattias har klamma!', 'exempt': False, 'created': '2022-04-20T11:15:08.978Z'} +TEST_GRADE_ATTEMPT = {'id': '_5340506_1', 'userId': '_111522_1', 'status': 'Completed', 'feedback': 'Great work man!', 'studentComments': 'hallaballa', 'exempt': False, 'created': '2022-04-07T12:01:20.708Z', 'attemptDate': '2022-04-07T12:01:20.708Z', 'modified': '2022-04-20T10:59:43.098Z'} + +class TestAssignmentsServices(object): + @classmethod + def setup_class(cls): + cls.test_session = requests.Session() + cls.mock_post_patcher = patch('bbcli.cli.requests.Session.post') + cls.mock_get_patcher = patch('bbcli.services.announcements_service.requests.Session.get') + cls.mock_auth_patcher = patch('bbcli.cli.authenticate_user') + cls.mock_update_patcher = patch('bbcli.cli.requests.Session.patch') + + cls.mock_post = cls.mock_post_patcher.start() + cls.mock_get = cls.mock_get_patcher.start() + cls.mock_auth = cls.mock_auth_patcher.start() + cls.mock_update = cls.mock_update_patcher.start() + + @classmethod + def teardown_class(cls): + cls.mock_post_patcher.stop() + cls.mock_get_patcher.stop() + cls.mock_auth_patcher.stop() + cls.mock_update_patcher.stop() + cls.test_session.close + + + def test_get_assignments(self): + self.mock_auth.return_value.ok = True + self.mock_get.return_value.ok = True + self.mock_get.return_value.text = json.dumps(TEST_ASSIGNMENTS_LIST) + + response = get_assignments(self.test_session, 'test_course_id') + + assert_equal(response, TEST_ASSIGNMENTS_LIST['results']) + + def test_get_column_attempts(self): + self.mock_auth.return_value.ok = True + self.mock_get.return_value.ok = True + self.mock_get.return_value.text = json.dumps(TEST_ASSIGNMENT_ATTEMPTS_LIST) + + response = get_column_attempts(self.test_session, 'test_course_id', 'test_column_id') + + assert_equal(json.dumps(response), json.dumps(TEST_ASSIGNMENT_ATTEMPTS_LIST['results'])) + + def test_get_column_attempt(self): + self.mock_auth.return_value.ok = True + self.mock_get.return_value.ok = True + self.mock_get.return_value.text = json.dumps(TEST_ATTEMPT) + + response = get_column_attempt(self.test_session, 'test_course_id', 'test_column_id', 'test_attempt_id') + + assert_equal(response, json.dumps(TEST_ATTEMPT, indent=2)) + + def create_column_attempt(self): + self.mock_auth.return_value.ok = True + self.mock_post.return_value.ok = True + self.mock_post.return_value.text = json.dumps(TEST_SUBMITTED_ATTEMPT) + + response = create_column_attempt(self.test_session, 'test_course_id', 'test_column_id') + + assert_equal(response, TEST_SUBMITTED_ATTEMPT) + + def test_update_column_attempt(self): + self.mock_auth.return_value.ok = True + self.mock_update.return_value.ok = True + self.mock_update.return_value.text = json.dumps(TEST_GRADE_ATTEMPT) + + response = update_column_attempt(self.test_session, 'test_course_id', 'test_column_id', 'test_attempt_id') + + assert_equal(response, TEST_GRADE_ATTEMPT) + diff --git a/tests/test_services/test_contents_services.py b/tests/test_services/test_contents_services.py index f0f709b7d555cf8c495caf1620340e22061c2812..087d2cc43e6023fe9caa1afe69367ef376fa749c 100644 --- a/tests/test_services/test_contents_services.py +++ b/tests/test_services/test_contents_services.py @@ -23,7 +23,7 @@ TEST_GET_CONTENT_UPDATED = {'id': '_1698141_1', 'parentId': '_1697863_1', 'title TEST_UPLOADED_FILE = {'id': '53-5321C30FA434825104FDC83B173BF720-abcdf665d3294daf8addddc56a670674'} -class TestAnnouncementsServices(object): +class TestContentsServices(object): @classmethod def setup_class(cls): cls.test_session = requests.Session()