diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e48be24..7ba82783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ PyCanvas Changelog ================== +Version 0.2 +----------- + +**New Endpoint Coverage** + +- Groups +- Roles +- Page Revisions +- Sections +- Conversations + +**General** + +- Standardized `__str__` methods. They now (generally) follow the convention of the value of the single most relevant field followed by an ID in parentheses. +- Reworked how `requests_mock` is used in test suite. +- Nested dictionaries are now allowed as kwargs +- Split 401 into two exceptions: `InvalidAccessToken` if `'WWW-Authenticate'` header is present. Otherwise, `Unauthorized`. + + +**Bugfixes** + +- Moved some incorrectly placed enrollment methods to the Enrollment class. +- Corrected `Process` class to `Progress` +- Minor text fixes. + Version 0.1.2 ------------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8f1ec16..7cd8de43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ Below you'll find guidelines for contributing that will keep our codebase clean ### Bug Reports #### Reporting bugs -Bug reports are awesome. Writing quality bug reports helps us identify issues and solve them even faster. You can submit bug reports directly to our [issue tracker](https://***REMOVED***/pycanvas/issues). +Bug reports are awesome. Writing quality bug reports helps us identify issues and solve them even faster. You can submit bug reports directly to our [issue tracker](https://example.com/changeme/pycanvas/issues). Here are a few things worth mentioning when making a report: @@ -34,11 +34,11 @@ Here are a few things worth mentioning when making a report: ### Resolving issues We welcome pull requests for bug fixes and new features! Feel free to browse our open, unassigned issues and assign yourself to them. You can also filter by labels: -* [simple](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=simple) -- easier issues to start working on; great for getting familiar with the codebase. -* [api coverage](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=api+coverage) -- covering new endpoints or updating existing ones. -* [internal](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=internal) -- updates to the engine to improve performance. -* [major](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=major) -- difficult or major changes or additions that require familiarity with the library. -* [bug](https://***REMOVED***/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=bug) -- happy little code accidents. +* [simple](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=simple) -- easier issues to start working on; great for getting familiar with the codebase. +* [api coverage](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=api+coverage) -- covering new endpoints or updating existing ones. +* [internal](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=internal) -- updates to the engine to improve performance. +* [major](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=major) -- difficult or major changes or additions that require familiarity with the library. +* [bug](https://example.com/changeme/pycanvas/issues?scope=all&sort=id_desc&state=opened&utf8=%E2%9C%93&label_name%5B%5D=bug) -- happy little code accidents. Once you've found an issue you're interested in tackling, take a look at our [first contribution tutorial](#making-your-first-contribution) for information on our pull request policy. @@ -49,7 +49,7 @@ Once you've found an issue you're interested in tackling, take a look at our [fi Now that you've selected an issue to work on, you'll need to set up an environment for writing code. We'll assume you already have pip, virtualenv, and git installed and are using a terminal. If not, please set those up before continuing. -1. Clone our repository by executing `git clone git@***REMOVED***/pycanvas.git` +1. Clone our repository by executing `git clone git@example.com:changeme/pycanvas.git` 2. Pull the latest commit from the **master** branch: `git pull origin master` 3. Create a new branch with the format **issue/[issue_number]-[issue-title]**: `git branch -b issue/1-test-issue-for-documentation` 4. Set up a new virtual environment ( `virtualenv env` ) and activate it (`source env/bin/activate`) @@ -179,7 +179,7 @@ pycanvas/util.py 22 0 100% TOTAL 629 0 100% ``` -Certain statements can be omitted from the coverage report by adding `# pragma: no cover` but this should be used conservatively. If your tests pass and your coverage is at 100%, you're ready to [submit a pull request](https://***REMOVED***/pycanvas/merge_requests)! +Certain statements can be omitted from the coverage report by adding `# pragma: no cover` but this should be used conservatively. If your tests pass and your coverage is at 100%, you're ready to [submit a pull request](https://example.com/changeme/pycanvas/merge_requests)! Be sure to include the issue number in the title with a pound sign in front of it (#123) so we know which issue the code is addressing. Point the branch at master and then submit it for review. diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 00000000..1934f908 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,35 @@ +PyCanvas Deploy Procedures +========================== + +Pre-Flight Checklist +-------------------- + +- On branch `master` and up-to-date +- All tests pass +- 100% coverage +- `CHANGELOG` is accurate + +Packaging +--------- + +Update version number in `setup.py`. + +Run `python setup.py sdist`. This should create a file in the `dist` directory called something like `pycanvas-0.0.0.tar.gz`. + +Generate Documentation +---------------------- + +In the `docs` directory, run `make clean html`. + +**TODO:** how to publish documentation. + +Deploy +------ + +Commit the new files and the changes to `setup.py` and push. + +Create a merge request from `master` to `stable`, and merge. + +Tag the merge commit with the version number: `git tag -a v0.0.0 -m "Release version 0.0.0" abc1234` + +Push the tag: `git push origin v0.0.0` diff --git a/README.md b/README.md index 65db52ac..de650f63 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ PyCanvas is a Python package that allows for simple access to the Instructure Ca ## Installation [internal only] -`pip install git+https://***REMOVED***/pycanvas.git@stable` +`pip install git+https://example.com/changeme/pycanvas.git@stable` ## Getting Started The first thing to do is open a connection with Canvas. You will need to provide the URL for the API endpoint of your Canvas instance as well as a valid API key. diff --git a/dist/pycanvas-0.2.tar.gz b/dist/pycanvas-0.2.tar.gz new file mode 100644 index 00000000..43f7eab7 Binary files /dev/null and b/dist/pycanvas-0.2.tar.gz differ diff --git a/docs/account-ref.rst b/docs/account-ref.rst index c90cb33b..e5e3ac31 100644 --- a/docs/account-ref.rst +++ b/docs/account-ref.rst @@ -3,4 +3,13 @@ Account ======= .. autoclass:: pycanvas.account.Account - :members: \ No newline at end of file + :members: + +.. autoclass:: pycanvas.account.AccountNotification + :members: + +.. autoclass:: pycanvas.account.AccountReport + :members: + +.. autoclass:: pycanvas.account.Role + :members: diff --git a/docs/class-reference.rst b/docs/class-reference.rst index 890472e7..830cdd3b 100644 --- a/docs/class-reference.rst +++ b/docs/class-reference.rst @@ -6,10 +6,13 @@ Class Reference canvas-ref account-ref assignment-ref + conversation-ref course-ref external-tool-ref + group-ref module-ref page-ref + progress-ref quiz-ref section-ref user-ref diff --git a/docs/conversation-ref.rst b/docs/conversation-ref.rst new file mode 100644 index 00000000..560ef4ec --- /dev/null +++ b/docs/conversation-ref.rst @@ -0,0 +1,6 @@ +============ +Conversation +============ + +.. autoclass:: pycanvas.conversation.Conversation + :members: diff --git a/docs/getting-started.rst b/docs/getting-started.rst index cfa324be..23f8f5a2 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -6,7 +6,7 @@ Installing PyCanvas [SoonTM] You will eventually be able to install with pip:: - pip install git+ssh://git@***REMOVED***/pycanvas.git + pip install git+ssh://git@example.com:changeme/pycanvas.git Usage ----- diff --git a/docs/group-ref.rst b/docs/group-ref.rst new file mode 100644 index 00000000..66d1a610 --- /dev/null +++ b/docs/group-ref.rst @@ -0,0 +1,6 @@ +===== +Group +===== + +.. autoclass:: pycanvas.group.Group + :members: diff --git a/docs/page-ref.rst b/docs/page-ref.rst index 1ca6d23b..bb132b62 100644 --- a/docs/page-ref.rst +++ b/docs/page-ref.rst @@ -4,3 +4,10 @@ Page .. autoclass:: pycanvas.page.Page :members: + + +============= +Page Revision +============= +.. autoclass:: pycanvas.page.PageRevision + :members: diff --git a/docs/process-ref.rst b/docs/process-ref.rst new file mode 100644 index 00000000..c8a4ba6a --- /dev/null +++ b/docs/process-ref.rst @@ -0,0 +1,6 @@ +============ +Progress +============ + +.. autoclass:: pycanvas.progress.Progress + :members: diff --git a/pycanvas/__init__.py b/pycanvas/__init__.py index ef3e9572..de512e8d 100644 --- a/pycanvas/__init__.py +++ b/pycanvas/__init__.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- + +__version__ = '0.2' + import exceptions import canvas import canvas_object diff --git a/pycanvas/account.py b/pycanvas/account.py index f77414ea..8fa4c8e4 100644 --- a/pycanvas/account.py +++ b/pycanvas/account.py @@ -1,16 +1,13 @@ -from canvas_object import CanvasObject -from exceptions import RequiredFieldMissing -from paginated_list import PaginatedList -from util import combine_kwargs, obj_or_id +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs, obj_or_id class Account(CanvasObject): def __str__(self): - return "id: %s, name: %s" % ( - self.id, - self.name - ) + return "{} ({})".format(self.name, self.id) def close_notification_for_user(self, user, notification): """ @@ -26,7 +23,7 @@ def close_notification_for_user(self, user, notification): :type notification: :class:`pycanvas.account.AccountNotification` or int :rtype: :class:`pycanvas.account.AccountNotification` """ - from user import User + from pycanvas.user import User user_id = obj_or_id(user, "user", (User,)) notif_id = obj_or_id(notification, "notif", (AccountNotification,)) @@ -62,7 +59,7 @@ def create_course(self, **kwargs): :rtype: :class:`pycanvas.course.Course` """ - from course import Course + from pycanvas.course import Course response = self._requester.request( 'POST', 'accounts/%s/courses' % (self.id), @@ -103,7 +100,7 @@ def create_user(self, pseudonym, **kwargs): :type pseudonym: dict :rtype: :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User if isinstance(pseudonym, dict) and 'unique_id' in pseudonym: kwargs['pseudonym'] = pseudonym @@ -134,7 +131,10 @@ def create_notification(self, account_notification, **kwargs): if isinstance(account_notification, dict) and required_keys_present: kwargs['account_notification'] = account_notification else: - raise RequiredFieldMissing("account_notification must be a dictionary with keys 'subject', 'message', 'start_at', and 'end_at'.") + raise RequiredFieldMissing(( + "account_notification must be a dictionary with keys " + "'subject', 'message', 'start_at', and 'end_at'." + )) response = self._requester.request( 'POST', @@ -162,7 +162,7 @@ def delete_user(self, user): :type user: :class:`pycanvas.user.User` or int :rtype: :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User user_id = obj_or_id(user, "user", (User,)) @@ -181,7 +181,7 @@ def get_courses(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.Course` """ - from course import Course + from pycanvas.course import Course return PaginatedList( Course, @@ -198,7 +198,7 @@ def get_external_tool(self, tool_id): :rtype: :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool response = self._requester.request( 'GET', @@ -214,9 +214,10 @@ def get_external_tools(self, **kwargs): :calls: `GET /api/v1/accounts/:account_id/external_tools \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.external_tool.ExternalTool` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool return PaginatedList( ExternalTool, @@ -236,7 +237,8 @@ def get_index_of_reports(self, report_type): :param report_type: The type of report. :type report_type: str - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.AccountReport` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.account.AccountReport` """ return PaginatedList( AccountReport, @@ -252,7 +254,8 @@ def get_reports(self): :calls: `GET /api/v1/accounts/:account_id/reports \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.AccountReport` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.account.AccountReport` """ return PaginatedList( AccountReport, @@ -290,7 +293,7 @@ def get_users(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User return PaginatedList( User, @@ -311,9 +314,10 @@ def get_user_notifications(self, user): :param user: The user object or ID to retrieve notifications for. :type user: :class:`pycanvas.user.User` or int - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.AccountNotification` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.account.AccountNotification` """ - from user import User + from pycanvas.user import User user_id = obj_or_id(user, "user", (User,)) @@ -346,7 +350,120 @@ def update(self, **kwargs): else: return False - def enroll_by_id(self, enrollment_id, **kwargs): + def list_roles(self, **kwargs): + """ + List the roles available to an account. + + :calls: `GET /api/v1/accounts/:account_id/roles \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.account.Role` + """ + + return PaginatedList( + Role, + self._requester, + 'GET', + 'accounts/%s/roles' % (self.id), + **combine_kwargs(**kwargs) + ) + + def get_role(self, role_id): + """ + Retrieve a role by ID. + + :calls: `GET /api/v1/accounts/:account_id/roles/:id \ + `_ + + :param role_id: The ID of the role. + :type role_id: int + :rtype: :class:`pycanvas.account.Role` + """ + + response = self._requester.request( + 'GET', + 'accounts/%s/roles/%s' % (self.id, role_id) + ) + return Role(self._requester, response.json()) + + def create_role(self, label, **kwargs): + """ + Create a new course-level or account-level role. + + :calls: `POST /api/v1/accounts/:account_id/roles \ + `_ + + :param label: The label for the role. + :type label: str + :rtype: :class:`pycanvas.account.Role` + """ + + response = self._requester.request( + 'POST', + 'accounts/%s/roles' % (self.id), + label=label, + **combine_kwargs(**kwargs) + ) + return Role(self._requester, response.json()) + + def deactivate_role(self, role_id, **kwargs): + """ + Deactivate a custom role. + + :calls: `DELETE /api/v1/accounts/:account_id/roles/:id \ + `_ + + :param role_id: The ID of the role. + :type role_id: int + :rtype: :class:`pycanvas.account.Role` + """ + + response = self._requester.request( + 'DELETE', + 'accounts/%s/roles/%s' % (self.id, role_id), + **combine_kwargs(**kwargs) + ) + return Role(self._requester, response.json()) + + def activate_role(self, role_id, **kwargs): + """ + Reactivate an inactive role. + + :calls: `POST /api/v1/accounts/:account_id/roles/:id/activate \ + `_ + + :param role_id: The ID of the role. + :type role_id: int + :rtype: :class:`pycanvas.account.Role` + """ + + response = self._requester.request( + 'POST', + 'accounts/%s/roles/%s/activate' % (self.id, role_id), + **combine_kwargs(**kwargs) + ) + return Role(self._requester, response.json()) + + def update_role(self, role_id, **kwargs): + """ + Update permissions for an existing role. + + :calls: `PUT /api/v1/accounts/:account_id/roles/:id \ + `_ + + :param role_id: The ID of the role. + :type role_id: int + :rtype: :class:`pycanvas.account.Role` + """ + + response = self._requester.request( + 'PUT', + 'accounts/%s/roles/%s' % (self.id, role_id), + **combine_kwargs(**kwargs) + ) + return Role(self._requester, response.json()) + + def get_enrollment(self, enrollment_id, **kwargs): """ Get an enrollment object by ID. @@ -357,7 +474,7 @@ def enroll_by_id(self, enrollment_id, **kwargs): :type enrollment_id: int :rtype: :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment response = self._requester.request( 'GET', @@ -366,18 +483,78 @@ def enroll_by_id(self, enrollment_id, **kwargs): ) return Enrollment(self._requester, response.json()) + def list_groups(self, **kwargs): + """ + Return a list of active groups for the specified account. + + :calls: `GET /api/v1/accounts/:account_id/groups \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.Group` + """ + from group import Group + return PaginatedList( + Group, + self._requester, + 'GET', + 'accounts/%s/groups' % (self.id), + **combine_kwargs(**kwargs) + ) + + def create_group_category(self, name, **kwargs): + """ + Create a Group Category + + :calls: `POST /api/v1/accounts/:account_id/group_categories \ + `_ + + :param name: Name of group category. + :type name: str + :rtype: :class:`pycanvas.group.GroupCategory` + """ + from group import GroupCategory + + response = self._requester.request( + 'POST', + 'accounts/%s/group_categories' % (self.id), + name=name, + **combine_kwargs(**kwargs) + ) + return GroupCategory(self._requester, response.json()) + + def list_group_categories(self): + """ + List group categories for a context + + :calls: `GET /api/v1/accounts/:account_id/group_categories \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupCategory` + """ + from group import GroupCategory + + return PaginatedList( + GroupCategory, + self._requester, + 'GET', + 'accounts/%s/group_categories' % (self.id) + ) + class AccountNotification(CanvasObject): + def __str__(self): # pragma: no cover - return "subject: %s, message: %s" % ( - self.subject, - self.message - ) + return str(self.subject) class AccountReport(CanvasObject): + def __str__(self): # pragma: no cover - return "id: %s, report: %s" % ( - self.id, - self.report - ) + return "{} ({})".format(self.report, self.id) + + +class Role(CanvasObject): + + def __str__(self): # pragma: no cover + return "{} ({})".format(self.label, self.base_role_type) diff --git a/pycanvas/assignment.py b/pycanvas/assignment.py index 2c5af1e8..645fa207 100644 --- a/pycanvas/assignment.py +++ b/pycanvas/assignment.py @@ -1,15 +1,11 @@ -from canvas_object import CanvasObject -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs class Assignment(CanvasObject): - def __str__(self): # pragma: no cover - return "id: %s, name: %s, description: %s" % ( - self.id, - self.name, - self.description - ) + def __str__(self): + return "{} ({})".format(self.name, self.id) def delete(self): """ diff --git a/pycanvas/avatar.py b/pycanvas/avatar.py index 4e10a23c..69d36aaa 100644 --- a/pycanvas/avatar.py +++ b/pycanvas/avatar.py @@ -1,11 +1,7 @@ -from canvas_object import CanvasObject +from pycanvas.canvas_object import CanvasObject class Avatar(CanvasObject): def __str__(self): # pragma: no cover - return "type: %s, display_name: %s, url: %s" % ( - self.type, - self.display_name, - self.url, - ) + return str(self.display_name) diff --git a/pycanvas/canvas.py b/pycanvas/canvas.py index 31b878b0..66b0a5ce 100644 --- a/pycanvas/canvas.py +++ b/pycanvas/canvas.py @@ -1,10 +1,10 @@ -from account import Account -from course import Course -from paginated_list import PaginatedList -from requester import Requester -from user import User -from group import Group -from util import combine_kwargs +from pycanvas.account import Account +from pycanvas.course import Course +from pycanvas.group import Group, GroupCategory +from pycanvas.paginated_list import PaginatedList +from pycanvas.requester import Requester +from pycanvas.user import User +from pycanvas.util import combine_kwargs class Canvas(object): @@ -12,16 +12,14 @@ class Canvas(object): The main class to be instantiated to provide access to Canvas's API. """ - def __init__(self, base_url, access_token, adapter=None): + def __init__(self, base_url, access_token): """ :param base_url: The base URL of the Canvas instance's API. :type base_url: str :param access_token: The API key to authenticate requests with. :type access_token: str - :param adapter: The requests_mock adapter (for testing). - :type adapter: :class:`requests_mock.Adapter` """ - self.__requester = Requester(base_url, access_token, adapter) + self.__requester = Requester(base_url, access_token) def create_account(self, **kwargs): """ @@ -120,7 +118,8 @@ def get_user(self, user_id, id_type=None): Retrieve a user by their ID. `id_type` denotes which endpoint to try as there are several different IDs that can pull the same user record from Canvas. - Refer to API documentation's `User `_ + Refer to API documentation's + `User `_ example to see the ID types a user can be retrieved with. :calls: `GET /users/:id \ @@ -213,9 +212,10 @@ def get_course_nicknames(self): :calls: `GET /api/v1/users/self/course_nicknames \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course_nickname.CourseNickname` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.course_nickname.CourseNickname` """ - from course import CourseNickname + from pycanvas.course import CourseNickname return PaginatedList( CourseNickname, @@ -235,7 +235,7 @@ def get_course_nickname(self, course_id): :type course_id: int :rtype: :class:`pycanvas.course_nickname.CourseNickname` """ - from course import CourseNickname + from pycanvas.course import CourseNickname response = self.__requester.request( 'GET', @@ -252,7 +252,7 @@ def get_section(self, section_id): :rtype: Section """ - from section import Section + from pycanvas.section import Section response = self.__requester.request( 'GET', 'sections/%s' % (section_id) @@ -274,7 +274,7 @@ def set_course_nickname(self, course_id, nickname): :type nickname: str :rtype: :class:`pycanvas.course_nickname.CourseNickname` """ - from course import CourseNickname + from pycanvas.course import CourseNickname response = self.__requester.request( 'PUT', @@ -316,6 +316,22 @@ def search_accounts(self, **kwargs): ) return response.json() + def create_group(self, **kwargs): + """ + Create a group + + :calls: `POST /api/v1/groups/ \ + `_ + + :rtype: :class:`pycanvas.group.Group` + """ + response = self.__requester.request( + 'POST', + 'groups', + **combine_kwargs(**kwargs) + ) + return Group(self.__requester, response.json()) + def get_group(self, group_id, **kwargs): """ Return the data for a single group. If the caller does not @@ -332,3 +348,189 @@ def get_group(self, group_id, **kwargs): **combine_kwargs(**kwargs) ) return Group(self.__requester, response.json()) + + def get_group_category(self, cat_id): + """ + Get a single group category. + + :calls: `GET /api/v1/group_categories/:group_category_id \ + `_ + + :rtype: :class:`pycanvas.group.GroupCategory` + """ + response = self.__requester.request( + 'GET', + 'group_categories/%s' % (cat_id) + ) + return GroupCategory(self.__requester, response.json()) + + def create_conversation(self, recipients, body, **kwargs): + """ + Create a new Conversation. + + :calls: `POST /api/v1/conversations \ + `_ + + :param recipients: An array of recipient ids. + These may be user ids or course/group ids prefixed + with 'course_' or 'group_' respectively, + e.g. recipients=['1', '2', 'course_3'] + :type recipients: `list` of `str` + :param body: The body of the message being added. + :type body: `str` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.conversation.Conversation` + """ + from pycanvas.conversation import Conversation + + return PaginatedList( + Conversation, + self.__requester, + 'POST', + 'conversations', + recipients=recipients, + body=body, + **combine_kwargs(**kwargs) + ) + + def get_conversation(self, conversation_id, **kwargs): + """ + Return single Conversation + + :calls: `GET /api/v1/conversations/:id \ + `_ + + :param conversation_id: The ID of the conversation. + :type conversation_id: `int` + :rtype: :class:`pycanvas.conversation.Conversation` + """ + from pycanvas.conversation import Conversation + response = self.__requester.request( + 'GET', + 'conversations/%s' % (conversation_id), + **combine_kwargs(**kwargs) + ) + return Conversation(self.__requester, response.json()) + + def get_conversations(self, **kwargs): + """ + Return list of conversations for the current user, most resent ones first. + + :calls: `GET /api/v1/conversations \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of \ + :class:`pycanvas.conversation.Conversation` + """ + from pycanvas.conversation import Conversation + + return PaginatedList( + Conversation, + self.__requester, + 'GET', + 'conversations', + **combine_kwargs(**kwargs) + ) + + def conversations_mark_all_as_read(self): + """ + Mark all conversations as read. + :calls: `POST /api/v1/conversations/mark_all_as_read \ + `_ + + :rtype: `bool` + """ + response = self.__requester.request( + 'POST', + 'conversations/mark_all_as_read' + ) + return response.json() == {} + + def conversations_unread_count(self): + """ + Get the number of unread conversations for the current user + + :calls: `GET /api/v1/conversations/unread_count \ + `_ + + :returns: simple object with unread_count, example: {'unread_count': '7'} + :rtype: `dict` + """ + response = self.__requester.request( + 'GET', + 'conversations/unread_count' + ) + + return response.json() + + def conversations_get_running_batches(self): + """ + Returns any currently running conversation batches for the current user. + Conversation batches are created when a bulk private message is sent + asynchronously. + + :calls: `GET /api/v1/conversations/batches \ + `_ + + :returns: dict with list of batch objects - not currently a Class + :rtype: `dict` + """ + + response = self.__requester.request( + 'GET', + 'conversations/batches' + ) + + return response.json() + + def conversations_batch_update(self, conversation_ids, event): + """ + + :calls: `PUT /api/v1/conversations \ + `_ + + :param conversation_ids: List of conversations to update. Limited to 500 conversations. + :type conversation_ids: `list` of `str` + :param event: The action to take on each conversation. + :type event: `str` + :rtype: :class:`pycanvas.progress.Progress` + """ + + from pycanvas.progress import Progress + + ALLOWED_EVENTS = [ + 'mark_as_read', + 'mark_as_unread', + 'star', + 'unstar', + 'archive', + 'destroy' + ] + + try: + if event not in ALLOWED_EVENTS: + raise ValueError( + '%s is not a valid action. Please use one of the following: %s' % ( + event, + ','.join(ALLOWED_EVENTS) + ) + ) + + if len(conversation_ids) > 500: + raise ValueError( + 'You have requested %s updates, which exceeds the limit of 500' % ( + len(conversation_ids) + ) + ) + + response = self.__requester.request( + 'PUT', + 'conversations', + event=event, + **{"conversation_ids[]": conversation_ids} + ) + return_progress = Progress(self.__requester, response.json()) + return return_progress + + except ValueError as e: + return e diff --git a/pycanvas/canvas_object.py b/pycanvas/canvas_object.py index c929088b..71c76203 100644 --- a/pycanvas/canvas_object.py +++ b/pycanvas/canvas_object.py @@ -69,7 +69,6 @@ def set_attributes(self, attributes): if DATE_PATTERN.match(str(value)): date = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') self.__setattr__(attribute + '_date', date) - # Non-unicode character. We can skip over this attribute. except UnicodeEncodeError: continue diff --git a/pycanvas/conversation.py b/pycanvas/conversation.py new file mode 100644 index 00000000..c0cd0ef8 --- /dev/null +++ b/pycanvas/conversation.py @@ -0,0 +1,111 @@ +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs + + +class Conversation(CanvasObject): + + def __str__(self): + return "{} ({})".format(self.subject, self.id) + + def edit(self, **kwargs): + """ + Update a conversation. + + :calls: `PUT /api/v1/conversations/:id \ + `_ + + :rtype: `bool` + """ + response = self._requester.request( + 'PUT', + 'conversations/%s' % (self.id), + **combine_kwargs(**kwargs) + ) + + if response.json().get('id'): + super(Conversation, self).set_attributes(response.json()) + return True + else: + return False + + def delete(self): + """ + Delete a conversation. + + :calls: `DELETE /api/v1/conversations/:id \ + `_ + + :rtype: `bool` + """ + response = self._requester.request( + 'DELETE', + 'conversations/%s' % (self.id) + ) + + if response.json().get('id'): + super(Conversation, self).set_attributes(response.json()) + return True + else: + return False + + def add_recipients(self, recipients): + """ + Add a recipient to a conversation. + + :calls: `POST /api/v1/conversations/:id/add_recipients \ + `_ + + :param recipients: A list of string format recipient ids. + These may be user ids or course/group ids prefixed + with 'course_' or 'group_' respectively, + e.g. recipients['1', '2', 'course_3'] + :type recipients: `list` of `str` + :rtype: :class:`pycanvas.account.Conversation` + """ + response = self._requester.request( + 'POST', + 'conversations/%s/add_recipients' % (self.id), + recipients=recipients + ) + return Conversation(self._requester, response.json()) + + def add_message(self, body, **kwargs): + """ + Add a message to a conversation. + + :calls: `POST /api/v1/conversations/:id/add_message \ + `_ + + :param body: The body of the conversation. + :type body: str + :returns: `pycanvas.account.Conversation` with only the most recent message. + :rtype: :class:`pycanvas.account.Conversation` + """ + response = self._requester.request( + 'POST', + 'conversations/%s/add_message' % (self.id), + body=body, + **combine_kwargs(**kwargs) + ) + return Conversation(self._requester, response.json()) + + def delete_messages(self, remove): + """ + Delete messages from this conversation. + + Note that this only affects this user's view of the conversation. + If all messages are deleted, the conversation will be as well. + + :calls: `POST /api/v1/conversations/:id/remove_messages \ + `_ + + :param remove: List of message ids to be removed. + :type remove: `list` of `str` + :rtype: `dict` + """ + response = self._requester.request( + 'POST', + 'conversations/%s/remove_messages' % (self.id), + remove=remove + ) + return response.json() diff --git a/pycanvas/course.py b/pycanvas/course.py index bb9abd74..d3b93153 100644 --- a/pycanvas/course.py +++ b/pycanvas/course.py @@ -1,15 +1,15 @@ -from canvas_object import CanvasObject -from exceptions import RequiredFieldMissing -from upload import Uploader -from util import combine_kwargs -from page import Page -from paginated_list import PaginatedList +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.page import Page +from pycanvas.paginated_list import PaginatedList +from pycanvas.upload import Uploader +from pycanvas.util import combine_kwargs class Course(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.course_code, self.name) + return "{} {} ({})".format(self.course_code, self.name, self.id) def conclude(self): """ @@ -82,7 +82,7 @@ def get_user(self, user_id, user_id_type=None): :type user_id_type: str :rtype: :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User if user_id_type: uri = 'courses/%s/users/%s:%s' % (self.id, user_id_type, user_id) @@ -107,7 +107,7 @@ def get_users(self, search_term=None, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User return PaginatedList( User, @@ -130,7 +130,7 @@ def enroll_user(self, user, enrollment_type, **kwargs): :type enrollment_type: str :rtype: :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment kwargs['enrollment[user_id]'] = user.id kwargs['enrollment[type]'] = enrollment_type @@ -153,7 +153,7 @@ def get_recent_students(self): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` """ - from user import User + from pycanvas.user import User return PaginatedList( User, @@ -256,9 +256,10 @@ def get_enrollments(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/enrollments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment return PaginatedList( Enrollment, self._requester, @@ -267,7 +268,7 @@ def get_enrollments(self, **kwargs): **combine_kwargs(**kwargs) ) - def get_assignment(self, assignment_id): + def get_assignment(self, assignment_id, **kwargs): """ Return the assignment with the given ID. @@ -278,30 +279,33 @@ def get_assignment(self, assignment_id): :type assignment_id: int :rtype: :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment response = self._requester.request( 'GET', 'courses/%s/assignments/%s' % (self.id, assignment_id), + **combine_kwargs(**kwargs) ) return Assignment(self._requester, response.json()) - def get_assignments(self): + def get_assignments(self, **kwargs): """ List all of the assignments in this course. :calls: `GET /api/v1/courses/:course_id/assignments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment return PaginatedList( Assignment, self._requester, 'GET', - 'courses/%s/assignments' % (self.id) + 'courses/%s/assignments' % (self.id), + **combine_kwargs(**kwargs) ) def create_assignment(self, assignment, **kwargs): @@ -317,7 +321,7 @@ def create_assignment(self, assignment, **kwargs): :type assignment: dict :rtype: :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment if isinstance(assignment, dict) and 'name' in assignment: kwargs['assignment'] = assignment @@ -341,7 +345,7 @@ def get_quizzes(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.quiz.Quiz` """ - from quiz import Quiz + from pycanvas.quiz import Quiz return PaginatedList( Quiz, self._requester, @@ -362,7 +366,7 @@ def get_quiz(self, quiz_id): :type quiz_id: int :rtype: :class:`pycanvas.quiz.Quiz` """ - from quiz import Quiz + from pycanvas.quiz import Quiz response = self._requester.request( 'GET', 'courses/%s/quizzes/%s' % (self.id, quiz_id) @@ -383,7 +387,7 @@ def create_quiz(self, quiz, **kwargs): :type quiz: dict :rtype: :class:`pycanvas.quiz.Quiz` """ - from quiz import Quiz + from pycanvas.quiz import Quiz if isinstance(quiz, dict) and 'title' in quiz: kwargs['quiz'] = quiz @@ -407,9 +411,10 @@ def get_modules(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/modules \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.module.Module` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.module.Module` """ - from module import Module + from pycanvas.module import Module return PaginatedList( Module, @@ -431,7 +436,7 @@ def get_module(self, module_id, **kwargs): :type module_id: int :rtype: :class:`pycanvas.module.Module` """ - from module import Module + from pycanvas.module import Module response = self._requester.request( 'GET', @@ -454,7 +459,7 @@ def create_module(self, module, **kwargs): :returns: The created module. :rtype: :class:`pycanvas.module.Module` """ - from module import Module + from pycanvas.module import Module if isinstance(module, dict) and 'name' in module: kwargs['module'] = module @@ -471,58 +476,6 @@ def create_module(self, module, **kwargs): return Module(self._requester, module_json) - def deactivate_enrollment(self, enrollment_id, task): - """ - Delete, conclude, or deactivate an enrollment. - - The following tasks can be performed on an enrollment: conclude, delete, \ - inactivate, deactivate. - - :calls: `DELETE /api/v1/courses/:course_id/enrollments/:id \ - `_ - - :param enrollment_id: The ID of the enrollment to modify. - :type enrollment_id: int - :param task: The task to perform on the enrollment. - :type task: str - :rtype: :class:`pycanvas.enrollment.Enrollment` - """ - from enrollment import Enrollment - - ALLOWED_TASKS = ['conclude', 'delete', 'inactivate', 'deactivate'] - - if not task in ALLOWED_TASKS: - raise ValueError('%s is not a valid task. Please use one of the following: %s' % ( - task, - ','.join(ALLOWED_TASKS) - )) - - response = self._requester.request( - 'DELETE', - 'courses/%s/enrollments/%s' % (self.id, enrollment_id), - task=task - ) - return Enrollment(self._requester, response.json()) - - def reactivate_enrollment(self, enrollment_id): - """ - Activate an inactive enrollment. - - :calls: `PUT /api/v1/courses/:course_id/enrollments/:id/reactivate \ - `_ - - :param enrollment_id: The ID of the enrollment to reactivate. - :type enrollment_id: int - :rtype: :class:`pycanvas.enrollment.Enrollment` - """ - from enrollment import Enrollment - - response = self._requester.request( - 'PUT', - 'courses/%s/enrollments/%s/reactivate' % (self.id, enrollment_id) - ) - return Enrollment(self._requester, response.json()) - def get_external_tool(self, tool_id): """ :calls: `GET /api/v1/courses/:course_id/external_tools/:external_tool_id \ @@ -530,7 +483,7 @@ def get_external_tool(self, tool_id): :rtype: :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool response = self._requester.request( 'GET', @@ -546,9 +499,10 @@ def get_external_tools(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/external_tools \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.external_tool.ExternalTool` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.external_tool.ExternalTool` """ - from external_tool import ExternalTool + from pycanvas.external_tool import ExternalTool return PaginatedList( ExternalTool, @@ -570,7 +524,7 @@ def get_section(self, section_id): :type section_id: int :rtype: :class:`pycanvas.section.Section` """ - from section import Section + from pycanvas.section import Section response = self._requester.request( 'GET', @@ -669,7 +623,7 @@ def get_page(self, url): `_ :param url: The url for the page. - :type url: string + :type url: str :returns: The specified page. :rtype: :class: `pycanvas.course.Course` """ @@ -683,15 +637,106 @@ def get_page(self, url): return Page(self._requester, page_json) + def list_sections(self, **kwargs): + """ + Returns the list of sections for this course. + + :calls: `GET /api/v1/courses/:course_id/sections \ + `_ + + :rtype: :class: `pycanvas.section.Section` + """ + from pycanvas.section import Section + return PaginatedList( + Section, + self._requester, + 'GET', + 'courses/%s/sections' % (self.id), + **combine_kwargs(**kwargs) + ) + + def create_course_section(self, **kwargs): + """ + Create a new section for this course. + + :calls: `POST /api/v1/courses/:course_id/sections \ + `_ + + :rtype: :class:`pycanvas.course.Section` + """ + + from pycanvas.section import Section + response = self._requester.request( + 'POST', + 'courses/%s/sections' % (self.id), + **combine_kwargs(**kwargs) + ) + + return Section(self._requester, response.json()) + + def list_groups(self, **kwargs): + """ + Return list of active groups for the specified course. + + :calls:`GET /api/v1/courses/:course_id/groups \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.Course` + """ + from group import Group + return PaginatedList( + Group, + self._requester, + 'GET', + 'courses/%s/groups' % (self.id), + **combine_kwargs(**kwargs) + ) + + def create_group_category(self, name, **kwargs): + """ + Create a group category. + + :calls: `POST /api/v1/courses/:course_id/group_categories \ + `_ + + :param name: Name of the category. + :type name: str + :rtype: :class:`pycanvas.group.GroupCategory` + """ + from pycanvas.group import GroupCategory + + response = self._requester.request( + 'POST', + 'courses/%s/group_categories' % (self.id), + name=name, + **combine_kwargs(**kwargs) + ) + return GroupCategory(self._requester, response.json()) + + def list_group_categories(self): + """ + List group categories for a context. + + :calls: `GET /api/v1/courses/:course_id/group_categories \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupCategory` + """ + from pycanvas.group import GroupCategory + + return PaginatedList( + GroupCategory, + self._requester, + 'GET', + 'courses/%s/group_categories' % (self.id) + ) + class CourseNickname(CanvasObject): def __str__(self): - return "course_id: %s, name: %s, nickname: %s, " % ( - self.course_id, - self.name, - self.nickname - ) + return "{} ({})".format(self.nickname, self.course_id) def remove(self): """ diff --git a/pycanvas/enrollment.py b/pycanvas/enrollment.py index 429b1211..91bb9cd0 100644 --- a/pycanvas/enrollment.py +++ b/pycanvas/enrollment.py @@ -1,12 +1,51 @@ -from canvas_object import CanvasObject +from pycanvas.canvas_object import CanvasObject class Enrollment(CanvasObject): - def __str__(self): # pragma: no cover - return "id: %s, course_id: %s, user_id: %s, type: %s, " % ( - self.id, - self.course_id, - self.user_id, - self.type + def __str__(self): + return "{} ({})".format(self.type, self.id) + + def deactivate(self, task): + """ + Delete, conclude, or deactivate an enrollment. + + The following tasks can be performed on an enrollment: conclude, delete, \ + inactivate, deactivate. + + :calls: `DELETE /api/v1/courses/:course_id/enrollments/:id \ + `_ + + :param task: The task to perform on the enrollment. + :type task: str + :rtype: :class:`pycanvas.enrollment.Enrollment` + """ + ALLOWED_TASKS = ['conclude', 'delete', 'inactivate', 'deactivate'] + + if task not in ALLOWED_TASKS: + raise ValueError('%s is not a valid task. Please use one of the following: %s' % ( + task, + ','.join(ALLOWED_TASKS) + )) + + response = self._requester.request( + 'DELETE', + 'courses/%s/enrollments/%s' % (self.course_id, self.id), + task=task + ) + return Enrollment(self._requester, response.json()) + + def reactivate(self): + """ + Activate an inactive enrollment. + + :calls: `PUT /api/v1/courses/:course_id/enrollments/:id/reactivate \ + `_ + + :rtype: :class:`pycanvas.enrollment.Enrollment` + """ + response = self._requester.request( + 'PUT', + 'courses/%s/enrollments/%s/reactivate' % (self.course_id, self.id) ) + return Enrollment(self._requester, response.json()) diff --git a/pycanvas/exceptions.py b/pycanvas/exceptions.py index 6d0a3a20..859171ac 100644 --- a/pycanvas/exceptions.py +++ b/pycanvas/exceptions.py @@ -1,8 +1,3 @@ -""" -A collection of PyCanvas exception classes. -""" - - class CanvasException(Exception): # pragma: no cover """ Base class for all errors returned by the Canvas API. @@ -15,7 +10,7 @@ def __init__(self, message): if errors: self.message = str(errors) else: - self.message = 'Something went wrong.' + self.message = ('Something went wrong. ', message) else: self.message = message @@ -33,7 +28,7 @@ class InvalidAccessToken(CanvasException): pass -class PermissionError(CanvasException): +class Unauthorized(CanvasException): """PyCanvas's key is valid, but is unauthorized to access the requested resource.""" pass diff --git a/pycanvas/external_tool.py b/pycanvas/external_tool.py index c4e599aa..2ebd37d3 100644 --- a/pycanvas/external_tool.py +++ b/pycanvas/external_tool.py @@ -1,12 +1,12 @@ -from canvas_object import CanvasObject -from exceptions import CanvasException -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import CanvasException +from pycanvas.util import combine_kwargs class ExternalTool(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.name, self.description) + return "{} ({})".format(self.name, self.id) @property def parent_id(self): @@ -42,8 +42,8 @@ def get_parent(self): :rtype: :class:`pycanvas.account.Account` or :class:`pycanvas.account.Course` """ - from account import Account - from course import Course + from pycanvas.account import Account + from pycanvas.course import Course response = self._requester.request( 'GET', diff --git a/pycanvas/group.py b/pycanvas/group.py index e5f429f1..112ab7d5 100644 --- a/pycanvas/group.py +++ b/pycanvas/group.py @@ -1,29 +1,39 @@ -from canvas_object import CanvasObject -from paginated_list import PaginatedList -from util import combine_kwargs -from exceptions import RequiredFieldMissing +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs class Group(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.name, self.description) + return "{} ({})".format(self.name, self.id) - def show_front_page(self): + def create_page(self, wiki_page, **kwargs): """ - Retrieve the content of the front page. + Create a new wiki page. - :calls: `GET /api/v1/groups/:group_id/front_page \ - `_ + :calls: `POST /api/v1/groups/:group_id/pages \ + `_ - :rtype: :class:`pycanvas.group.Group` + :param wiki_page: Details about the page to create. + :type wiki_page: dict + :returns: The created page. + :rtype: :class: `pycanvas.page.Page` """ - from course import Page + from pycanvas.course import Page + + if isinstance(wiki_page, dict) and 'title' in wiki_page: + kwargs['wiki_page'] = wiki_page + else: + raise RequiredFieldMissing("Dictionary with key 'title' is required.") response = self._requester.request( - 'GET', - 'groups/%s/front_page' % (self.id) + 'POST', + 'groups/%s/pages' % (self.id), + **combine_kwargs(**kwargs) ) + page_json = response.json() page_json.update({'group_id': self.id}) @@ -36,9 +46,9 @@ def edit_front_page(self, **kwargs): :calls: `PUT /api/v1/groups/:group_id/front_page \ `_ - :rtype: :class:`pycanvas.group.Group` + :rtype: :class:`pycanvas.page.Page` """ - from course import Page + from pycanvas.course import Page response = self._requester.request( 'PUT', @@ -50,6 +60,49 @@ def edit_front_page(self, **kwargs): return Page(self._requester, page_json) + def show_front_page(self): + """ + Retrieve the content of the front page. + + :calls: `GET /api/v1/groups/:group_id/front_page \ + `_ + + :rtype: :class:`pycanvas.group.Group` + """ + from pycanvas.course import Page + + response = self._requester.request( + 'GET', + 'groups/%s/front_page' % (self.id) + ) + page_json = response.json() + page_json.update({'group_id': self.id}) + + return Page(self._requester, page_json) + + def get_page(self, url): + """ + Retrieve the contents of a wiki page. + + :calls: `GET /api/v1/groups/:group_id/pages/:url \ + `_ + + :param url: The url for the page. + :type url: str + :returns: The specified page. + :rtype: :class: `pycanvas.groups.Group` + """ + from pycanvas.course import Page + + response = self._requester.request( + 'GET', + 'groups/%s/pages/%s' % (self.id, url) + ) + page_json = response.json() + page_json.update({'group_id': self.id}) + + return Page(self._requester, page_json) + def get_pages(self, **kwargs): """ List the wiki pages associated with a group. @@ -57,9 +110,10 @@ def get_pages(self, **kwargs): :calls: `GET /api/v1/groups/:group_id/pages \ `_ - :rtype: :class:`pycanvas.groups.Group` + :rtype: :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.page.Page` """ - from course import Page + from pycanvas.course import Page return PaginatedList( Page, self._requester, @@ -69,54 +123,404 @@ def get_pages(self, **kwargs): **combine_kwargs(**kwargs) ) - def create_page(self, wiki_page, **kwargs): + def edit(self, **kwargs): """ - Create a new wiki page. + Edit a group. - :calls: `POST /api/v1/groups/:group_id/pages \ - `_ + :calls: `PUT /api/v1/groups/:group_id \ + `_ - :param title: The title for the page. - :type title: dict - :returns: The created page. - :rtype: :class: `pycanvas.groups.Group` + :rtype: :class:`pycanvas.group.Group` """ - from course import Page + response = self._requester.request( + 'PUT', + 'groups/%s' % (self.id), + **combine_kwargs(**kwargs) + ) + return Group(self._requester, response.json()) - if isinstance(wiki_page, dict) and 'title' in wiki_page: - kwargs['wiki_page'] = wiki_page - else: - raise RequiredFieldMissing("Dictionary with key 'title' is required.") + def delete(self): + """ + Delete a group. + :calls: `DELETE /api/v1/groups/:group_id \ + `_ + + :rtype: :class:`pycanvas.group.Group` + """ response = self._requester.request( + 'DELETE', + 'groups/%s' % (self.id) + ) + return Group(self._requester, response.json()) + + def invite(self, invitees): + """ + Invite users to group. + + :calls: `POST /api/v1/groups/:group_id/invite \ + `_ + + :param invitees: list of user ids + :type invitees: integer list + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupMembership` + """ + return PaginatedList( + GroupMembership, + self._requester, 'POST', - 'groups/%s/pages' % (self.id), + 'groups/%s/invite' % (self.id), + invitees=invitees + ) + + def list_users(self, **kwargs): + """ + List users in a group. + + :calls: `POST /api/v1/groups/:group_id/users \ + `_ + + :param invitees: list of user ids + :type invitees: integer list + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.user.User` + """ + from pycanvas.user import User + return PaginatedList( + User, + self._requester, + 'GET', + 'groups/%s/users' % (self.id), **combine_kwargs(**kwargs) ) - page_json = response.json() - page_json.update({'group_id': self.id}) + def remove_user(self, user): + """ + Leave a group if allowed. - return Page(self._requester, page_json) + :calls: `DELETE /api/v1/groups/:group_id/:type/:id \ + `_ - def get_page(self, url): + :param user: The user object or ID to remove from the group. + :type user: :class:`pycanvas.user.User` or int + + :rtype: :class:`pycanvas.user.User` """ - Retrieve the contents of a wiki page. - :calls: `GET /api/v1/groups/:group_id/pages/:url \ - `_ + from pycanvas.user import User + from pycanvas.util import obj_or_id - :param url: The url for the page. - :type url: string - :returns: The specified page. - :rtype: :class: `pycanvas.groups.Group` + user_id = obj_or_id(user, "user", (User,)) + + response = self._requester.request( + 'DELETE', + 'groups/%s/users/%s' % (self.id, user_id), + ) + return User(self._requester, response.json()) + + def upload(self, file, **kwargs): + """ + Upload a file to the group. + Only those with the 'Manage Files' permission on a group can upload files to the group. + By default, this is anybody participating in the group, or any admin over the group. + + :calls: `POST /api/v1/groups/:group_id/files \ + `_ + + :param path: The path of the file to upload. + :type path: str + :param file: The file or path of the file to upload. + :type file: file or str + :returns: True if the file uploaded successfully, False otherwise, \ + and the JSON response from the API. + :rtype: tuple + """ + from pycanvas.upload import Uploader + + return Uploader( + self._requester, + 'groups/%s/files' % (self.id), + file, + **kwargs + ).start() + + def preview_html(self, html): """ - from course import Page + Preview HTML content processed for this course. + :calls: `POST /api/v1/groups/:group_id/preview_html \ + `_ + + :param html: The HTML code to preview. + :type html: str + :rtype: str + """ + response = self._requester.request( + 'POST', + 'groups/%s/preview_html' % (self.id), + html=html + ) + return response.json().get('html', '') + + def get_activity_stream_summary(self): + """ + Return a summary of the current user's global activity stream. + + :calls: `GET /api/v1/groups/:group_id/activity_stream/summary \ + `_ + + :rtype: dict + """ response = self._requester.request( 'GET', - 'groups/%s/pages/%s' % (self.id, url) + 'groups/%s/activity_stream/summary' % (self.id) ) - page_json = response.json() - page_json.update({'group_id': self.id}) + return response.json() - return Page(self._requester, page_json) + def list_memberships(self, **kwargs): + """ + List users in a group. + + :calls: `GET /api/v1/groups/:group_id/memberships \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.GroupMembership` + """ + return PaginatedList( + GroupMembership, + self._requester, + 'GET', + 'groups/%s/memberships' % (self.id), + **combine_kwargs(**kwargs) + ) + + def get_membership(self, user_id, membership_type): + """ + List users in a group. + + :calls: `GET /api/v1/groups/:group_id/users/:user_id or \ + /api/v1/groups/:group_id/memberships/:membership_id \ + `_ + + :param invitees: list of user ids + :type invitees: integer list + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'GET', + 'groups/%s/%s/%s' % (self.id, membership_type, user_id) + ) + return GroupMembership(self._requester, response.json()) + + def create_membership(self, user_id, **kwargs): + """ + Join, or request to join, a group, depending on the join_level of the group. + If the membership or join request already exists, then it is simply returned. + + :calls: `POST /api/v1/groups/:group_id/memberships \ + `_ + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'POST', + 'groups/%s/memberships' % (self.id), + user_id=user_id, + **combine_kwargs(**kwargs) + ) + return GroupMembership(self._requester, response.json()) + + def update_membership(self, user_id, **kwargs): + """ + Accept a membership request, or add/remove moderator rights. + + :calls: `PUT /api/v1/groups/:group_id/users/:user_id \ + `_ + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'PUT', + 'groups/%s/users/%s' % (self.id, user_id), + **combine_kwargs(**kwargs) + ) + return GroupMembership(self._requester, response.json()) + + +class GroupMembership(CanvasObject): + + def __str__(self): + return "{} - {} ({})".format(self.user_id, self.group_id, self.id) + + def update(self, mem_id, **kwargs): + """ + Accept a membership request, or add/remove moderator rights. + + :calls: `PUT /api/v1/groups/:group_id/memberships/:membership_id \ + `_ + + :rtype: :class:`pycanvas.group.GroupMembership` + """ + response = self._requester.request( + 'PUT', + 'groups/%s/memberships/%s' % (self.id, mem_id), + **combine_kwargs(**kwargs) + ) + return GroupMembership(self._requester, response.json()) + + def remove_user(self, user): + """ + Remove user from membership. + + :calls: `DELETE /api/v1/groups/:group_id/:type/:id \ + `_ + + :param user: The user object or ID to remove from the group. + :type user: :class:`pycanvas.user.User` or int + + :rtype: empty dict + """ + from pycanvas.user import User + from pycanvas.util import obj_or_id + + user_id = obj_or_id(user, "user", (User,)) + + response = self._requester.request( + 'DELETE', + 'groups/%s/users/%s' % (self.id, user_id), + ) + return response.json() + + def remove_self(self): + """ + Leave a group if allowed. + + :calls: `DELETE /api/v1/groups/:group_id/:type/:id \ + `_ + + :rtype: empty dict + """ + response = self._requester.request( + 'DELETE', + 'groups/%s/memberships/self' % (self.id), + ) + return response.json() + + +class GroupCategory(CanvasObject): + + def __str__(self): + return "{} ({})".format(self.name, self.id) + + def create_group(self, **kwargs): + """ + Create a group. + + :calls: `POST /api/v1/group_categories/:group_category_id/groups \ + `_ + + :rtype: :class:`pycanvas.group.Group` + """ + response = self._requester.request( + 'POST', + 'group_categories/%s/groups' % (self.id), + **combine_kwargs(**kwargs) + ) + return Group(self._requester, response.json()) + + def update(self, **kwargs): + """ + Update a group category. + + :calls: `PUT /api/v1/group_categories/:group_category_id \ + `_ + + :rtype: :class:`pycanvas.group.GroupCategory` + """ + response = self._requester.request( + 'PUT', + 'group_categories/%s' % (self.id), + **combine_kwargs(**kwargs) + ) + return GroupCategory(self._requester, response.json()) + + def delete(self): + """ + Delete a group category. + + :calls: `DELETE /api/v1/group_categories/:group_category_id \ + `_ + + :rtype: empty dict + """ + response = self._requester.request( + 'DELETE', + 'group_categories/%s' % (self.id) + ) + return response.json() + + def list_groups(self): + """ + List groups in group category. + + :calls: `GET /api/v1/group_categories/:group_category_id/groups \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.group.Group` + """ + return PaginatedList( + Group, + self._requester, + 'GET', + 'group_categories/%s/groups' % (self.id) + ) + + def list_users(self, **kwargs): + """ + List users in group category. + + :calls: `GET /api/v1/group_categories/:group_category_id/users \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.user.User` + """ + from pycanvas.user import User + return PaginatedList( + User, + self._requester, + 'GET', + 'group_categories/%s/users' % (self.id), + **combine_kwargs(**kwargs) + ) + + def assign_members(self, sync=False): + """ + Assign unassigned members. + + :calls: `POST /api/v1/group_categories/:group_category_id/assign_unassigned_members \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.user.User` + or :class:`pycanvas.progress.Progress` + """ + from pycanvas.user import User + from pycanvas.progress import Progress + if sync: + return PaginatedList( + User, + self._requester, + 'POST', + 'group_categories/%s/assign_unassigned_members' % (self.id) + ) + else: + response = self._requester.request( + 'POST', + 'group_categories/%s/assign_unassigned_members' % (self.id) + ) + return Progress(self._requester, response.json()) diff --git a/pycanvas/module.py b/pycanvas/module.py index 0341cc3e..5466f14d 100644 --- a/pycanvas/module.py +++ b/pycanvas/module.py @@ -1,16 +1,13 @@ -from canvas_object import CanvasObject -from exceptions import RequiredFieldMissing -from paginated_list import PaginatedList -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs class Module(CanvasObject): def __str__(self): - return "id: %s, name: %s" % ( - self.id, - self.name, - ) + return "{} ({})".format(self.name, self.id) def edit(self, **kwargs): """ @@ -78,7 +75,8 @@ def list_module_items(self, **kwargs): :calls: `GET /api/v1/courses/:course_id/modules/:module_id/items \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.module.ModuleItem` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.module.ModuleItem` """ return PaginatedList( ModuleItem, @@ -142,11 +140,7 @@ def create_module_item(self, module_item, **kwargs): class ModuleItem(CanvasObject): def __str__(self): - return "id: %s, title: %s, description: %s" % ( - self.id, - self.title, - self.module_id - ) + return "{} ({})".format(self.title, self.id) def edit(self, **kwargs): """ diff --git a/pycanvas/page.py b/pycanvas/page.py index 8764a2c6..811bec87 100644 --- a/pycanvas/page.py +++ b/pycanvas/page.py @@ -1,15 +1,12 @@ -from canvas_object import CanvasObject - -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs +from pycanvas.paginated_list import PaginatedList class Page(CanvasObject): def __str__(self): - return "url: %s, title: %s" % ( - self.url, - self.title - ) + return "{} ({})".format(self.title, self.url) def edit(self, **kwargs): """ @@ -19,7 +16,7 @@ def edit(self, **kwargs): :calls: `PUT /api/v1/courses/:course_id/pages/:url \ `_ - :rtype: :class: `pycanvas.course.Course` + :rtype: :class:`pycanvas.course.Course` """ response = self._requester.request( 'PUT', @@ -40,7 +37,7 @@ def delete(self): :calls: `DELETE /api/v1/courses/:course_id/pages/:url \ `_ - :rtype: :class: `pycanvas.course.Course` + :rtype: :class:`pycanvas.course.Course` """ response = self._requester.request( 'DELETE', @@ -82,8 +79,154 @@ def get_parent(self): :rtype: :class:`pycanvas.group.Group` or :class:`pycanvas.course.Course` """ - from group import Group - from course import Course + from pycanvas.group import Group + from pycanvas.course import Course + + response = self._requester.request( + 'GET', + '%ss/%s' % (self.parent_type, self.parent_id) + ) + + if self.parent_type == 'group': + return Group(self._requester, response.json()) + elif self.parent_type == 'course': + return Course(self._requester, response.json()) + + def show_latest_revision(self, **kwargs): + """ + Retrieve the contents of the latest revision. + + :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions/latest \ + `_ + + :rtype: :class:`pycanvas.pagerevision.PageRevision` + """ + response = self._requester.request( + 'GET', + '%ss/%s/pages/%s/revisions/latest' % (self.parent_type, self.parent_id, self.url), + **combine_kwargs(**kwargs) + ) + return PageRevision(self._requester, response.json()) + + def get_revision_by_id(self, revision_id, **kwargs): + """ + Retrieve the contents of the revision by the id. + + :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions/:revision_id \ + `_ + + :param revision_id: The id of a specified revision. + :type revision_id: int + :returns: Contents of the page revision. + :rtype: :class:`pycanvas.pagerevision.PageRevision` + """ + response = self._requester.request( + 'GET', + '%ss/%s/pages/%s/revisions/%s' % ( + self.parent_type, + self.parent_id, + self.url, + revision_id + ), + **combine_kwargs(**kwargs) + ) + pagerev_json = response.json() + if self.parent_type == "group": + pagerev_json.update({'group_id': self.id}) + elif self.parent_type == "course": + pagerev_json.update({'course_id': self.id}) + + return PageRevision(self._requester, pagerev_json) + + def list_revisions(self, **kwargs): + """ + List the revisions of a page. + + :calls: `GET /api/v1/courses/:course_id/pages/:url/revisions \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.pagerevision.PageRevision` + """ + return PaginatedList( + PageRevision, + self._requester, + 'GET', + '%ss/%s/pages/%s/revisions' % (self.parent_type, self.parent_id, self.url), + **combine_kwargs(**kwargs) + ) + + def revert_to_revision(self, revision_id): + """ + Revert the page back to a specified revision. + + :calls: `POST /api/v1/courses/:course_id/pages/:url/revisions/:revision_id \ + `_ + + :param revision_id: The id of a specified revision. + :type revision_id: int + :returns: Contents of the page revision. + :rtype: :class:`pycanvas.pagerevision.PageRevision` + """ + response = self._requester.request( + 'POST', + '%ss/%s/pages/%s/revisions/%s' % ( + self.parent_type, + self.parent_id, + self.url, + revision_id + ), + ) + pagerev_json = response.json() + if self.parent_type == "group": + pagerev_json.update({'group_id': self.id}) + elif self.parent_type == "course": + pagerev_json.update({'group_id': self.id}) + + return PageRevision(self._requester, pagerev_json) + + +class PageRevision(CanvasObject): + + def __str__(self): + return "{} ({})".format(self.updated_at, self.revision_id) + + @property + def parent_id(self): + """ + Return the id of the course or group that spawned this page. + + :rtype: int + """ + if hasattr(self, 'course_id'): + return self.course_id + elif hasattr(self, 'group_id'): + return self.group_id + else: + raise ValueError("Page does not have a course_id or group_id") + + @property + def parent_type(self): + """ + Return whether the page was spawned from a course or group. + + :rtype: str + """ + if hasattr(self, 'course_id'): + return 'course' + elif hasattr(self, 'group_id'): + return 'group' + else: + raise ValueError("ExternalTool does not have a course_id or group_id") + + def get_parent(self): + """ + Return the object that spawned this page. + + :rtype: :class:`pycanvas.group.Group` or :class:`pycanvas.course.Course` + """ + from pycanvas.group import Group + from pycanvas.course import Course response = self._requester.request( 'GET', diff --git a/pycanvas/page_view.py b/pycanvas/page_view.py index 2b4e49e8..118af4bb 100644 --- a/pycanvas/page_view.py +++ b/pycanvas/page_view.py @@ -1,7 +1,7 @@ -from canvas_object import CanvasObject +from pycanvas.canvas_object import CanvasObject class PageView(CanvasObject): def __str__(self): - return "%s %s %s" % (self.id, self.url, self.created_at) + return "{} ({})".format(self.context_type, self.id) diff --git a/pycanvas/paginated_list.py b/pycanvas/paginated_list.py index 8241441c..de940691 100644 --- a/pycanvas/paginated_list.py +++ b/pycanvas/paginated_list.py @@ -17,6 +17,7 @@ def __init__(self, content_class, requester, request_method, first_url, extra_at self.__next_url = first_url self.__next_params = self.__first_params self.__extra_attribs = extra_attribs or {} + self.__request_method = request_method def __getitem__(self, index): assert isinstance(index, (int, slice)) @@ -54,7 +55,7 @@ def _has_next(self): def _get_next_page(self): response = self.__requester.request( - 'GET', + self.__request_method, self.__next_url, **self.__next_params ) diff --git a/pycanvas/progress.py b/pycanvas/progress.py new file mode 100644 index 00000000..ff50456b --- /dev/null +++ b/pycanvas/progress.py @@ -0,0 +1,26 @@ +from pycanvas.canvas_object import CanvasObject + + +class Progress(CanvasObject): + + def __str__(self): + return "{} - {} ({})".format(self.tag, self.workflow_state, self.id) + + def query(self): + """ + Return completion and status information about an asynchronous job. + + :calls: `GET /api/v1/progress/:id \ + `_ + + :rtype: :class:`pycanvas.progress.Progress` + """ + response = self._requester.request( + 'GET', + 'progress/%s' % (self.id) + ) + response_json = response.json() + + super(Progress, self).set_attributes(response_json) + + return Progress(self._requester, response_json) diff --git a/pycanvas/quiz.py b/pycanvas/quiz.py index 545cba79..1537e3e0 100644 --- a/pycanvas/quiz.py +++ b/pycanvas/quiz.py @@ -1,14 +1,11 @@ -from canvas_object import CanvasObject -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.util import combine_kwargs class Quiz(CanvasObject): def __str__(self): - return "id %s, title: %s" % ( - self.id, - self.title - ) + return "{} ({})".format(self.title, self.id) def edit(self, **kwargs): """ diff --git a/pycanvas/requester.py b/pycanvas/requester.py index 944d8225..0889722f 100644 --- a/pycanvas/requester.py +++ b/pycanvas/requester.py @@ -1,28 +1,27 @@ -from pycanvas.exceptions import BadRequest, CanvasException, PermissionError, ResourceDoesNotExist import requests +from pycanvas.exceptions import ( + BadRequest, CanvasException, InvalidAccessToken, ResourceDoesNotExist, + Unauthorized +) + class Requester(object): """ Responsible for handling HTTP requests. """ - def __init__(self, base_url, access_token, mock_adapter): + def __init__(self, base_url, access_token): """ :param base_url: The base URL of the Canvas instance's API. :type base_url: str :param access_token: The API key to authenticate requests with. :type access_token: str - :param mock_adapter: The requests_mock adapter (for testing). - :type mock_adapter: :class:`requests_mock.Adapter` """ self.base_url = base_url self.access_token = access_token self._session = requests.Session() - if mock_adapter: - self._session.mount('mock', mock_adapter) - def request(self, method, endpoint=None, headers=None, use_auth=True, url=None, **kwargs): """ Make a request to the Canvas API and return the response. @@ -64,7 +63,10 @@ def request(self, method, endpoint=None, headers=None, use_auth=True, url=None, if response.status_code == 400: raise BadRequest(response.json()) elif response.status_code == 401: - raise PermissionError(response.json()) + if 'WWW-Authenticate' in response.headers: + raise InvalidAccessToken(response.json()) + else: + raise Unauthorized(response.json()) elif response.status_code == 404: raise ResourceDoesNotExist('Not Found') elif response.status_code == 500: diff --git a/pycanvas/section.py b/pycanvas/section.py index c1c5fc46..fab1ff43 100644 --- a/pycanvas/section.py +++ b/pycanvas/section.py @@ -1,15 +1,15 @@ -from canvas_object import CanvasObject -from paginated_list import PaginatedList -from util import combine_kwargs +from pycanvas.canvas_object import CanvasObject +from pycanvas.paginated_list import PaginatedList +from pycanvas.util import combine_kwargs class Section(CanvasObject): def __str__(self): - return 'Section #%s \"%s\" |course_id: %s' % ( - self.id, + return '{} - {} ({})'.format( self.name, - self.course_id + self.course_id, + self.id, ) def get_enrollments(self, **kwargs): @@ -19,9 +19,10 @@ def get_enrollments(self, **kwargs): :calls: `GET /api/v1/sections/:section_id/enrollments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment return PaginatedList( Enrollment, @@ -30,3 +31,64 @@ def get_enrollments(self, **kwargs): 'sections/%s/enrollments' % (self.id), **combine_kwargs(**kwargs) ) + + def cross_list_section(self, new_course_id): + """ + Move the Section to another course. + + :calls: `POST /api/v1/sections/:id/crosslist/:new_course_id + \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + response = self._requester.request( + 'POST', + 'sections/%s/crosslist/%s' % (self.id, new_course_id) + ) + return Section(self._requester, response.json()) + + def decross_list_section(self): + """ + Undo cross-listing of a section. + + :calls: `DELETE /api/v1/sections/:id/crosslist \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + response = self._requester.request( + "DELETE", + "sections/%s/crosslist" % (self.id) + ) + return Section(self._requester, response.json()) + + def edit(self): + """ + Edit contents of a target section. + + :calls: `PUT /api/v1/sections/:id \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + response = self._requester.request( + "PUT", + "sections/%s" % (self.id) + ) + return Section(self._requester, response.json()) + + def delete(self): + """ + Delete a target section. + + :calls: `DELETE /api/v1/sections/:id \ + `_ + + :rtype: :class:`pycanvas.section.Section` + """ + response = self._requester.request( + "DELETE", + "sections/%s" % (self.id) + ) + return Section(self._requester, response.json()) diff --git a/pycanvas/upload.py b/pycanvas/upload.py index f191847a..060b9929 100644 --- a/pycanvas/upload.py +++ b/pycanvas/upload.py @@ -1,6 +1,6 @@ import os -from util import combine_kwargs +from pycanvas.util import combine_kwargs class Uploader(object): @@ -54,10 +54,10 @@ def upload(self, response): """ response = response.json() if not response.get('upload_url'): - raise Exception('Bad API response. No upload_url.') + raise ValueError('Bad API response. No upload_url.') if not response.get('upload_params'): - raise Exception('Bad API response. No upload_params.') + raise ValueError('Bad API response. No upload_params.') kwargs = response.get('upload_params') kwargs['file'] = self.file diff --git a/pycanvas/user.py b/pycanvas/user.py index d46c3027..c270f5e5 100644 --- a/pycanvas/user.py +++ b/pycanvas/user.py @@ -1,13 +1,13 @@ -from canvas_object import CanvasObject -from paginated_list import PaginatedList -from upload import Uploader -from util import combine_kwargs, obj_or_id +from pycanvas.canvas_object import CanvasObject +from pycanvas.paginated_list import PaginatedList +from pycanvas.upload import Uploader +from pycanvas.util import combine_kwargs, obj_or_id class User(CanvasObject): def __str__(self): - return "%s" % (self.name) + return "{} ({})".format(self.name, self.id) def get_profile(self, **kwargs): """ @@ -33,7 +33,7 @@ def get_page_views(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.PageView` """ - from page_view import PageView + from pycanvas.page_view import PageView return PaginatedList( PageView, @@ -52,7 +52,7 @@ def get_courses(self, **kwargs): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.course.Course` """ - from course import Course + from pycanvas.course import Course return PaginatedList( Course, @@ -70,9 +70,10 @@ def get_missing_submissions(self): :calls: `GET /api/v1/users/:user_id/missing_submissions \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment return PaginatedList( Assignment, @@ -202,7 +203,7 @@ def get_avatars(self): :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.avatar.Avatar` """ - from avatar import Avatar + from pycanvas.avatar import Avatar return PaginatedList( Avatar, @@ -219,9 +220,10 @@ def get_assignments(self, course_id, **kwargs): :calls: `GET /api/v1/users/:user_id/courses/:course_id/assignments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.assignment.Assignment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.assignment.Assignment` """ - from assignment import Assignment + from pycanvas.assignment import Assignment return PaginatedList( Assignment, @@ -238,9 +240,10 @@ def get_enrollments(self, **kwargs): :calls: `GET /api/v1/users/:user_id/enrollments \ `_ - :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.enrollment.Enrollment` + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of + :class:`pycanvas.enrollment.Enrollment` """ - from enrollment import Enrollment + from pycanvas.enrollment import Enrollment return PaginatedList( Enrollment, @@ -275,3 +278,22 @@ def upload(self, file, **kwargs): file, **kwargs ).start() + + def list_groups(self, **kwargs): + """ + Return the list of active groups for the user. + + :calls:`GET /api/v1/users/self/groups \ + `_ + + :rtype: :class:`pycanvas.paginated_list.PaginatedList` of :class:`pycanvas.group.Group` + """ + from group import Group + + return PaginatedList( + Group, + self._requester, + 'GET', + 'users/self/groups', + **combine_kwargs(**kwargs) + ) diff --git a/pycanvas/util.py b/pycanvas/util.py index 6ab41421..1ded18f1 100644 --- a/pycanvas/util.py +++ b/pycanvas/util.py @@ -1,24 +1,30 @@ -"""A collection of useful methods.""" - - def combine_kwargs(**kwargs): - # TODO: look into implementing and testing multi-level post params - # e.g. `account[settings][restrict_student_future_view]` """ Combines a list of keyword arguments into a single dictionary. :rtype: dict """ - data = {} - for key, value in kwargs.iteritems(): + def flatten_dict(prefix, key, value): + new_prefix = prefix + '[' + str(key) + ']' if isinstance(value, dict): - for subkey, subvalue in value.iteritems(): - data[key + '[' + subkey + ']'] = subvalue - continue - - data[key] = value - - return data + d = {} + for k, v in value.iteritems(): + d.update(flatten_dict(new_prefix, k, v)) + return d + else: + return {new_prefix: value} + + combined_kwargs = {} + + # Loop through all kwargs. + for kw, arg in kwargs.iteritems(): + if isinstance(arg, dict): + # If the argument is a dictionary, flatten it. + for key, value in arg.iteritems(): + combined_kwargs.update(flatten_dict(str(kw), key, value)) + else: + combined_kwargs.update({str(kw): arg}) + return combined_kwargs def obj_or_id(parameter, param_name, object_types): @@ -33,7 +39,6 @@ def obj_or_id(parameter, param_name, object_types): :param object_types: tuple :rtype: int """ - try: return int(parameter) except: diff --git a/setup.py b/setup.py index 2615463d..f72d6f66 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,37 @@ +import re from setuptools import setup +# get version number +with open('pycanvas/__init__.py', 'r') as fd: + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + fd.read(), + re.MULTILINE + ).group(1) + +if not version: + raise RuntimeError('Cannot find version information') + setup( name='pycanvas', - version='0.1.2', + version=version, description='API wrapper for the Canvas LMS', - url='https://***REMOVED***/pycanvas/', + url='https://github.com/ucfopen/PyCanvas', author='Techrangers (University of Central Florida)', - author_email='***REMOVED***', - license='Some cool UCF license', + author_email='pycanvas@example.com', + license='MIT License', packages=['pycanvas'], + include_package_data=True, install_requires=['requests'], - zip_safe=False + zip_safe=False, + classifiers=[ + 'Development Status :: 2 - Pre-Alpha' + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: MIT License' + 'Operating System :: OS Independent' + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries', + ], ) diff --git a/tests/fixtures/account.json b/tests/fixtures/account.json index 02ce96f3..139ac7b3 100644 --- a/tests/fixtures/account.json +++ b/tests/fixtures/account.json @@ -108,17 +108,6 @@ } ] }, - "enroll_by_id": { - "method": "GET", - "endpoint": "accounts/1/enrollments/1", - "data": { - "id": 1, - "course_id": 1, - "user_id": 1, - "type": "StudentEnrollment" - }, - "status_code": 200 - }, "get_by_id": { "method": "GET", "endpoint": "accounts/1", @@ -136,24 +125,9 @@ }, "get_by_id_2": { "method": "GET", - "endpoint": "accounts/100", - "data": { - "id": 100, - "name": "Old Name", - "parent_account_id": null, - "root_account_id": null, - "default_storage_quota_mb": 500, - "default_user_storage_quota_mb": 50, - "default_group_storage_quota_mb": 50, - "default_time_zone": "America/Denver" - }, - "status_code": 200 - }, - "get_by_id_3": { - "method": "GET", - "endpoint": "accounts/101", + "endpoint": "accounts/1", "data": { - "id": 101, + "id": 1, "name": "Old Name", "parent_account_id": null, "root_account_id": null, @@ -190,7 +164,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -245,7 +219,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -274,6 +248,39 @@ ], "status_code": 200 }, + "list_groups_context": { + "method": "GET", + "endpoint": "accounts/1/groups", + "data": [ + { + "id": 1, + "name": "Group 1" + }, + { + "id": 2, + "name": "Group 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_groups_context2": { + "method": "GET", + "endpoint": "accounts/1/groups?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Group 3" + }, + { + "id": 4, + "name": "Group 4" + } + ], + "status_code": 200 + }, "multiple": { "method": "GET", "endpoint": "accounts", @@ -340,7 +347,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -371,7 +378,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -404,7 +411,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -423,6 +430,16 @@ ], "status_code": 200 }, + "get_role": { + "method": "GET", + "endpoint": "accounts/1/roles/2", + "data": { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + }, "users": { "method": "GET", "endpoint": "accounts/1/users", @@ -437,7 +454,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -470,7 +487,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -491,9 +508,9 @@ }, "update": { "method": "PUT", - "endpoint": "accounts/100", + "endpoint": "accounts/1", "data": { - "id": 100, + "id": 1, "name": "Updated Name", "parent_account_id": null, "root_account_id": null, @@ -506,8 +523,130 @@ }, "update_fail": { "method": "PUT", - "endpoint": "accounts/101", + "endpoint": "accounts/1", "data": {}, "status_code": 200 + }, + "create_group_category": { + "method": "POST", + "endpoint": "accounts/1/group_categories", + "data": { + "id": 1, + "name": "Test String", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 1, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "list_group_categories": { + "method": "GET", + "endpoint": "accounts/1/group_categories", + "data": [ + { + "id": 2, + "name": "Math Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 2, + "group_limit": null, + "progress": null + }, + { + "id": 3, + "name": "Film Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 3, + "group_limit": null, + "progress": null + } + ], + "status_code": 200 + }, + "list_roles": { + "method": "GET", + "endpoint": "accounts/1/roles", + "data": [ + { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + { + "id": 3, + "role": "StudentEnrollment1", + "label": "Student1" + } + ], + "headers": { + "Link": "; rel=\"next\"" + }, + "status_code": 200 + }, + "list_roles_2": { + "method": "GET", + "endpoint": "accounts/1/roles/?page=2&per_page=2", + "data": [ + { + "id": 5, + "role": "StudentEnrollment2", + "label": "Student2" + }, + { + "id": 7, + "role": "StudentEnrollment3", + "label": "Student3" + } + ], + "status_code": 200 + }, + "create_role": { + "method": "POST", + "endpoint": "accounts/1/roles", + "data":{ + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + }, + "deactivate_role": { + "method": "DELETE", + "endpoint": "accounts/1/roles/2", + "data": { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + }, + "activate_role": { + "method": "POST", + "endpoint": "accounts/1/roles/2/activate", + "data": { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 + }, + "update_role": { + "method": "PUT", + "endpoint": "accounts/1/roles/2", + "data": { + "id": 1, + "role": "StudentEnrollment", + "label": "Student" + }, + "status_code": 200 } } diff --git a/tests/fixtures/conversation.json b/tests/fixtures/conversation.json new file mode 100644 index 00000000..00b3e684 --- /dev/null +++ b/tests/fixtures/conversation.json @@ -0,0 +1,210 @@ +{ + "get_by_id": { + "method": "GET", + "endpoint": "conversations/1", + "data": { + "id": 1, + "subject": "Amazing Conversation" + }, + "status_code": 200 + }, + "get_by_id_2": { + "method": "GET", + "endpoint": "conversations/2", + "data": { + "id": 2, + "subject": "Slightly Entertaining Conversation" + }, + "status_code": 200 + }, + "get_conversations": { + "method": "GET", + "endpoint": "conversations", + "data": [ + { + "id": 1, + "subject": "Amazing Conversation" + }, + { + "id": 2, + "subject": "Slightly Entertaining Conversation" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "get_conversations_2": { + "method": "GET", + "endpoint": "conversations?page=2&per_page=2", + "data": [ + { + "id": 3, + "subject": "Boring Conversation" + }, + { + "id": 4, + "subject": "Average Conversation" + } + ], + "status_code": 200 + }, + "create_conversation": { + "method": "POST", + "endpoint": "conversations", + "data": [ + { + "recipients": ["1", "2"], + "body": "Test Conversation Body" + }, + { + "recipients": ["3", "4"], + "body": "Test Conversation Body 2" + } + ], + "status_code": 200 + }, + "edit_conversation": { + "method": "PUT", + "endpoint": "conversations/1", + "data": { + "id": 1, + "subject": "conversations api example" + }, + "status_code": 200 + }, + "edit_conversation_fail": { + "method": "PUT", + "endpoint": "conversations/2", + "data": { + "subject": "this should fail" + } + }, + "delete_conversation": { + "method": "DELETE", + "endpoint": "conversations/1", + "data": { + "id": 1, + "subject": "conversations api example" + }, + "status_code": 200 + }, + "delete_conversation_fail": { + "method": "DELETE", + "endpoint": "conversations/2", + "data": { + "subject": "this should fail" + } + }, + "add_recipients": { + "method": "POST", + "endpoint": "conversations/1/add_recipients", + "data": { + "id": 1, + "subject": "add_recipients test", + "messages": [ + { + "id": 1, + "body": "Bob was added to the conversation by Hank TA" + }, + { + "id": 2, + "body": "Joe was added to the conversation by Hank TA" + } + ] + }, + "status_code": 200 + }, + "add_message": { + "method": "POST", + "endpoint": "conversations/1/add_message", + "data": { + "id": 1, + "subject": "add_message test subject", + "workflow_state": "unread", + "messages": + [ + { + "id": 3, + "body": "add_message test body", + "author_id": 2, + "generated": false + } + ] + }, + "status_code": 200 + }, + "delete_message": { + "method": "POST", + "endpoint": "conversations/1/remove_messages", + "data": { + "id": 1, + "subject": "delete_message example", + "workflow_state": "read", + "last_message": "delete_message message", + "message_count": 1, + "properties": ["attachments"] + }, + "status_code": 200 + }, + "mark_all_as_read": { + "method": "POST", + "endpoint": "conversations/mark_all_as_read", + "data": {}, + "status_code": 200 + }, + "unread_count": { + "method": "GET", + "endpoint": "conversations/unread_count", + "data": { + "unread_count": "7" + }, + "status_code": 200 + }, + "get_running_batches": { + "method": "GET", + "endpoint": "conversations/batches", + "data":[ + { + "id": 1, + "subject": "conversations api example", + "message": + { + "id": 1, + "body": "quick reminder, no class tomorrow", + "author_id": 1 + } + }, + { + "id": 2, + "subject": "conversations api example", + "message": + { + "id": 2, + "body": "quick reminder, no class tomorrow", + "author_id": 1 + } + } + ], + "status_code": 200 + }, + "batch_update": { + "method": "PUT", + "endpoint": "conversations", + "data": { + "id": 1, + "context_id": 1, + "context_type": "Account", + "user_id": 123, + "tag": "course_batch_update", + "completion": 100, + "workflow_state": "completed", + "created_at": "2013-01-15T15:00:00Z", + "updated_at": "2013-01-15T15:04:00Z", + "message": "17 courses processed", + "url": "https://canvas.example.edu/api/v1/progress/1" + }, + "status_code": 200 + } +} diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json index 5767d5d3..2595ba85 100644 --- a/tests/fixtures/course.json +++ b/tests/fixtures/course.json @@ -1,5 +1,13 @@ { - "create": { + "conclude": { + "method": "DELETE", + "endpoint": "courses/1", + "data": { + "conclude": true + }, + "status_code": 200 + }, + "create_quiz": { "method": "POST", "endpoint": "courses/1/quizzes", "data": { @@ -19,14 +27,11 @@ }, "status_code": 200 }, - "deactivate_enrollment": { + "delete": { "method": "DELETE", - "endpoint": "courses/1/enrollments/1", + "endpoint": "courses/1", "data": { - "id": 1, - "course_id": 1, - "user_id": 1, - "type": "StudentEnrollment" + "delete": true }, "status_code": 200 }, @@ -60,7 +65,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_all_assignments2": { @@ -147,7 +152,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -198,7 +203,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -237,11 +242,11 @@ }, "get_user_id_type": { "method": "GET", - "endpoint": "courses/1/users/sis_login_id:ab123456", + "endpoint": "courses/1/users/sis_login_id:SISLOGIN", "data": { "id": 123456, "name": "Abby Smith", - "sis_login_id": "ab123456" + "sis_login_id": "SISLOGIN" }, "status_code": 200 }, @@ -259,7 +264,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -284,16 +289,20 @@ "data": [ { "id": 1, - "course_id": 5 + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment" }, { "id": 2, - "course_id": 6 + "course_id": 1, + "user_id": 2, + "type": "TeacherEnrollment" } ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_enrollments_2": { @@ -302,11 +311,15 @@ "data": [ { "id": 3, - "course_id": 7 + "course_id": 1, + "user_id": 10, + "type": "StudentEnrollment" }, { "id": 4, - "course_id": 8 + "course_id": 1, + "user_id": 8, + "type": "StudentEnrollment" } ], "status_code": 200 @@ -326,7 +339,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_quizzes2": { @@ -370,7 +383,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -409,17 +422,6 @@ }, "status_code": 200 }, - "reactivate_enrollment": { - "method": "PUT", - "endpoint": "courses/1/enrollments/1/reactivate", - "data": { - "id": 1, - "course_id": 1, - "user_id": 1, - "type": "StudentEnrollment" - }, - "status_code": 200 - }, "reset": { "method": "POST", "endpoint": "courses/1/reset_content", @@ -519,14 +521,14 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "upload": { "method": "POST", "endpoint": "courses/1/files", "data": { - "upload_url": "mock://example.com/api/v1/files/upload_response_upload_url", + "upload_url": "http://example.com/api/v1/files/upload_response_upload_url", "upload_params": { "some_param": "param123", "a_different_param": "param456" @@ -616,7 +618,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_pages2": { @@ -675,5 +677,125 @@ "url": "my-url" }, "status_code": 200 + }, + "list_sections": { + "method": "GET", + "endpoint": "courses/1/sections", + "data": [ + { + "id": 1, + "name": "Section 1" + }, + { + "id": 2, + "name": "Section 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_sections2": { + "method": "GET", + "endpoint": "courses/1/list_sections?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Section 3" + }, + { + "id": 4, + "name": "Section 4" + } + ], + "status_code": 200 + }, + "create_section": { + "method": "POST", + "endpoint": "courses/1/sections", + "data": { + "id": 1, + "name": "New Section" + }, + "status_code": 200 + }, + "list_groups_context": { + "method": "GET", + "endpoint": "courses/1/groups", + "data": [ + { + "id": 1, + "name": "Group 1" + }, + { + "id": 2, + "name": "Group 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_groups_context2": { + "method": "GET", + "endpoint": "courses/1/groups?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Group 3" + }, + { + "id": 4, + "name": "Group 4" + } + ], + "status_code": 200 + }, + "create_group_category": { + "method": "POST", + "endpoint": "courses/1/group_categories", + "data": { + "id": 1, + "name": "Test String", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 1, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "list_group_categories": { + "method": "GET", + "endpoint": "courses/1/group_categories", + "data": [ + { + "id": 2, + "name": "Math Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Course", + "account_id": 2, + "group_limit": null, + "progress": null + }, + { + "id": 3, + "name": "Film Groups", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Course", + "account_id": 3, + "group_limit": null, + "progress": null + } + ], + "status_code": 200 } } \ No newline at end of file diff --git a/tests/fixtures/enrollment.json b/tests/fixtures/enrollment.json new file mode 100644 index 00000000..d665daf6 --- /dev/null +++ b/tests/fixtures/enrollment.json @@ -0,0 +1,37 @@ +{ + "deactivate": { + "method": "DELETE", + "endpoint": "courses/1/enrollments/1", + "data": { + "id": 1, + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment", + "state": "inactive" + }, + "status_code": 200 + }, + "get_by_id": { + "method": "GET", + "endpoint": "accounts/1/enrollments/1", + "data": { + "id": 1, + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment" + }, + "status_code": 200 + }, + "reactivate": { + "method": "PUT", + "endpoint": "courses/1/enrollments/1/reactivate", + "data": { + "id": 1, + "course_id": 1, + "user_id": 1, + "type": "StudentEnrollment", + "state": "active" + }, + "status_code": 200 + } +} \ No newline at end of file diff --git a/tests/fixtures/external_tool.json b/tests/fixtures/external_tool.json index 2ba0c28f..0f53e04c 100644 --- a/tests/fixtures/external_tool.json +++ b/tests/fixtures/external_tool.json @@ -6,7 +6,7 @@ "id": 1, "name": "External Tool #1 (Account)", "description": "This is an external tool for an account.", - "url": "mock://www.example.com/ims/lti", + "url": "http://www.example.com/ims/lti", "privacy_level": "anonymous", "created_at": "2015-01-01T01:01:01Z", "updated_at": "2016-06-17T14:20:00Z" @@ -20,7 +20,7 @@ "id": 1, "name": "External Tool #1 (Course)", "description": "This is an external tool in a course.", - "url": "mock://www.example.com/ims/lti", + "url": "http://www.example.com/ims/lti", "privacy_level": "anonymous", "created_at": "2015-01-01T01:01:01Z", "updated_at": "2016-06-17T14:20:00Z" @@ -34,7 +34,7 @@ "id": 2, "name": "External Tool #2 (Course)", "description": "This is an external tool in a course.", - "url": "mock://www.example.com/ims/lti", + "url": "http://www.example.com/ims/lti", "privacy_level": "anonymous", "created_at": "2015-01-01T01:01:01Z", "updated_at": "2016-06-17T14:20:00Z" @@ -47,7 +47,7 @@ "data": { "id": "1", "name": "External Tool #1 (Course)", - "url": "mock://example.com/courses/1/external_tools/sessionless_launch/?verifier=1337" + "url": "http://example.com/courses/1/external_tools/sessionless_launch/?verifier=1337" }, "status_code": 200 }, diff --git a/tests/fixtures/group.json b/tests/fixtures/group.json index 8e5fe53c..7a76eb31 100644 --- a/tests/fixtures/group.json +++ b/tests/fixtures/group.json @@ -1,15 +1,15 @@ { - "show_front_page": { - "method": "GET", - "endpoint": "groups/1/front_page", - "data":{ + "create": { + "method": "POST", + "endpoint": "groups", + "data": { "id": 1, - "url": "front-page", - "title": "Front Page" + "name": "group0", + "description": "first group ever" }, "status_code": 200 }, - "get_single_group": { + "get_by_id": { "method": "GET", "endpoint": "groups/1", "data": { @@ -19,6 +19,26 @@ }, "status_code": 200 }, + "pages_get_page": { + "method": "GET", + "endpoint": "groups/1/pages/my-url", + "data": { + "id": 1, + "url": "my-url", + "title": "Awesome Page" + }, + "status_code": 200 + }, + "create_page": { + "method": "POST", + "endpoint": "groups/1/pages", + "data": { + "id": 2, + "url": "new-page", + "title": "New Page" + }, + "status_code": 200 + }, "edit_front_page": { "method": "PUT", "endpoint": "groups/1/front_page", @@ -29,6 +49,26 @@ }, "status_code": 200 }, + "show_front_page": { + "method": "GET", + "endpoint": "groups/1/front_page", + "data":{ + "id": 1, + "url": "front-page", + "title": "Front Page" + }, + "status_code": 200 + }, + "get_page": { + "method": "GET", + "endpoint": "groups/1/pages/my-url", + "data": { + "id": 1, + "url": "my-url", + "title": "Awesome Page" + }, + "status_code": 200 + }, "get_pages": { "method": "GET", "endpoint": "groups/1/pages", @@ -46,7 +86,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_pages2": { @@ -66,33 +106,485 @@ ], "status_code": 200 }, - "create_page": { + "edit": { + "method": "PUT", + "endpoint": "groups/1", + "data": { + "id": 1, + "name": "group-none", + "description": "New Group" + }, + "status_code": 200 + }, + "delete": { + "method": "DELETE", + "endpoint": "groups/1", + "data": { + "id": 1, + "name": "group-none", + "description": "most deleted group ever" + }, + "status_code": 200 + }, + "invite": { "method": "POST", - "endpoint": "groups/1/pages", + "endpoint": "groups/1/invite", + "data": [ + { + "id": 4, + "group_id": 1, + "workflow_state": "invited", + "user_id": 1, + "moderator": false, + "sis_import_id": null + }, + { + "id": 5, + "group_id": 1, + "workflow_state": "invited", + "user_id": 2, + "moderator": false, + "sis_import_id": null + } + ], + "status_code": 200 + }, + "list_users": { + "method": "GET", + "endpoint": "groups/1/users", + "data": [ + { + "id": 1, + "name": "Jack Doe" + }, + { + "id": 2, + "name": "Jim Doe" + } + ], + "headers": { + "Link": "; rel=\"next\"" + }, + "status_code": 200 + }, + "list_users_p2": { + "method": "GET", + "endpoint": "groups/1/users?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "John Smith" + }, + { + "id": 4, + "name": "Joe Smith" + } + ], + "status_code": 200 + }, + "upload": { + "method": "POST", + "endpoint": "groups/1/files", "data": { - "id": 2, - "url": "new-page", - "title": "New Page" + "upload_url": "http://example.com/api/v1/files/upload_response_upload_url", + "upload_params": { + "some_param": "param123", + "a_different_param": "param456" + } + } + }, + "upload_final": { + "method": "POST", + "endpoint": "files/upload_response_upload_url", + "data": { + "url": "great_url_success" + } + }, + "preview_processed_html": { + "method": "POST", + "endpoint": "groups/1/preview_html", + "data": { + "html": "

processed html

" }, "status_code": 200 }, - "get_page": { + "activity_stream_summary": { "method": "GET", - "endpoint": "groups/1/pages/my-url", + "endpoint": "groups/1/activity_stream/summary", + "data": [ + { + "type": "DiscussionTopic", + "unread_count": 2, + "count": 7 + }, + { + "type": "Conversation", + "unread_count": 0, + "count": 3 + } + ], + "status_code": 200 + }, + "list_memberships": { + "method": "GET", + "endpoint": "groups/1/memberships", + "data": [ + { + "id": 1, + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + { + "id": 2, + "group_id": 2, + "user_id": 2, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 2 + } + ], + "headers": { + "Link": "; rel=\"next\"" + }, + "status_code": 200 + }, + "list_memberships_p2": { + "method": "GET", + "endpoint": "groups/1/memberships?page=2&per_page=2", + "data": [ + { + "id": 3, + "group_id": 3, + "user_id": 3, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 3 + }, + { + "id": 4, + "group_id": 4, + "user_id": 4, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 4 + } + ], + "status_code": 200 + }, + "get_membership": { + "method": "GET", + "endpoint": "groups/1/users/1", "data": { "id": 1, - "url": "my-url", - "title": "Awesome Page" + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + "status_code": 200 + }, + "create_membership": { + "method": "POST", + "endpoint": "groups/1/memberships", + "data": { + "id": 1, + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 }, "status_code": 200 }, - "edit_page": { + "update_membership_user": { "method": "PUT", - "endpoint": "groups/1/pages/my-url", + "endpoint": "groups/1/users/1", + "data": { + "id": 1, + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + "status_code": 200 + }, + "update_membership_membership": { + "method": "PUT", + "endpoint": "groups/1/memberships/1", + "data": { + "id": 1, + "group_id": 1, + "user_id": 1, + "workflow_state": "accepted", + "moderator": true, + "just_created": true, + "sis_import_id": 1 + }, + "status_code": 200 + }, + "remove_user": { + "method": "DELETE", + "endpoint": "groups/1/users/1", + "data": {}, + "status_code": 200 + }, + "remove_self": { + "method": "DELETE", + "endpoint": "groups/1/memberships/self", + "data": {}, + "status_code": 200 + }, + "category_create_group": { + "method": "POST", + "endpoint": "group_categories/1/groups", + "data": { + "id": 1, + "name": "Test Create Group", + "description": null, + "is_public": false, + "followed_by_user": false, + "join_level": "invitation_only", + "members_count": 0, + "avatar_url": "https:///files/avatar_image.png", + "context_type": "Course", + "course_id": 3, + "role": null, + "group_category_id": 4, + "sis_group_id": "group4a", + "sis_import_id": 14, + "storage_quota_mb": 50, + "permissions": {"create_discussion_topic":true,"create_announcement":true} + }, + "status_code": 200 + }, + "get_category_by_id": { + "method": "GET", + "endpoint": "group_categories/1", + "data": { + "id": 1, + "name": "Test Get Category", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 3, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "category_update": { + "method": "PUT", + "endpoint": "group_categories/1", "data": { "id": 1, - "title": "New Page", - "url": "my-url" + "name": "Test Update Category", + "role": "communities", + "self_signup": null, + "auto_leader": null, + "context_type": "Account", + "account_id": 3, + "group_limit": null, + "progress": null + }, + "status_code": 200 + }, + "category_delete_category": { + "method": "DELETE", + "endpoint": "group_categories/1", + "data": { + + }, + "status_code": 200 + }, + "category_list_groups": { + "method": "GET", + "endpoint": "group_categories/1/groups", + "data": [ + { + "id": 1, + "name": "Math Group 1", + "description": null, + "is_public": false, + "followed_by_user": false, + "join_level": "invitation_only", + "members_count": 0, + "avatar_url": "https:///files/avatar_image.png", + "context_type": "Course", + "course_id": 3, + "role": null, + "group_category_id": 4, + "sis_group_id": "group4a", + "sis_import_id": 14, + "storage_quota_mb": 50, + "permissions": { + "create_discussion_topic":true, + "create_announcement":true + } + }, + { + "id": 2, + "name": "Math Group 1", + "description": null, + "is_public": false, + "followed_by_user": false, + "join_level": "invitation_only", + "members_count": 0, + "avatar_url": "https:///files/avatar_image.png", + "context_type": "Course", + "course_id": 3, + "role": null, + "group_category_id": 4, + "sis_group_id": "group4a", + "sis_import_id": 14, + "storage_quota_mb": 50, + "permissions": { + "create_discussion_topic":true, + "create_announcement":true + } + } + ], + "status_code": 200 + }, + "category_list_users": { + "method": "GET", + "endpoint": "group_categories/1/users", + "data": [ + { + "user_id": 1, + "name": "Sam", + "display_name": "Sam", + "sections": [ + { + "section_id": 1, + "section_code": "Section 1" + } + ] + }, + { + "user_id": 2, + "name": "Sue", + "display_name": "Sue", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + }, + { + "user_id": 3, + "name": "Joe", + "display_name": "Joe", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + }, + { + "user_id": 4, + "name": "Cecil", + "display_name": "Cecil", + "sections": [ + { + "section_id": 3, + "section_code": "Section 3" + } + ] + } + ], + "status_code": 200 + }, + "category_assign_members_true": { + "method": "POST", + "endpoint": "group_categories/1/assign_unassigned_members", + "data": [ + { + "id": 1, + "new_members": [ + { + "user_id": 1, + "name": "Sam", + "display_name": "Sam", + "sections": [ + { + "section_id": 1, + "section_code": "Section 1" + } + ] + }, + { + "user_id": 2, + "name": "Sue", + "display_name": "Sue", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + } + ] + }, + { + "id": 2, + "new_members": [ + { + "user_id": 3, + "name": "Joe", + "display_name": "Joe", + "sections": [ + { + "section_id": 2, + "section_code": "Section 2" + } + ] + }, + { + "user_id": 4, + "name": "Cecil", + "display_name": "Cecil", + "sections": [ + { + "section_id": 3, + "section_code": "Section 3" + } + ] + } + ] + } + ], + "status_code": 200 + }, + "category_assign_members_false": { + "method": "POST", + "endpoint": "group_categories/1/assign_unassigned_members", + "data": { + "completion": 0, + "context_id": 20, + "context_type": "GroupCategory", + "created_at": "2013-07-05T10:57:48-06:00", + "id": 2, + "message": null, + "tag": "assign_unassigned_members", + "updated_at": "2013-07-05T10:57:48-06:00", + "user_id": null, + "workflow_state": "running", + "url": "http://localhost:3000/api/v1/progress/2" }, "status_code": 200 } diff --git a/tests/fixtures/module.json b/tests/fixtures/module.json index 98efb38d..9369b5a5 100644 --- a/tests/fixtures/module.json +++ b/tests/fixtures/module.json @@ -52,7 +52,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_module_items2": { diff --git a/tests/fixtures/page.json b/tests/fixtures/page.json new file mode 100644 index 00000000..02f2e3eb --- /dev/null +++ b/tests/fixtures/page.json @@ -0,0 +1,116 @@ +{ + "get_page": { + "method": "GET", + "endpoint": "courses/1/pages/my-url", + "data": { + "id": 1, + "url": "my-url", + "title": "Awesome Page" + }, + "status_code": 200 + }, + "edit": { + "method": "PUT", + "endpoint": "courses/1/pages/my-url", + "data": { + "id": 1, + "title": "New Page", + "url": "my-url" + }, + "status_code": 200 + }, + "delete_page": { + "method": "DELETE", + "endpoint": "courses/1/pages/my-url", + "data": { + "id": 1, + "title": "Page To Be Deleted", + "url": "my-url" + }, + "status_code": 200 + }, + "list_revisions": { + "method": "GET", + "endpoint": "courses/1/pages/my-url/revisions", + "data": [ + { + "id": 1, + "title": "Revision 1" + }, + { + "id": 2, + "title": "Revision 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_revisions2": { + "method": "GET", + "endpoint": "courses/1/pages/my-url/revisions?page=2&per_page=2", + "data": [ + { + "id": 3, + "title": "Revision 3" + }, + { + "id": 4, + "title": "Revision 4" + } + ], + "status_code": 200 + }, + "latest_revision": { + "method": "GET", + "endpoint": "courses/1/pages/my-url/revisions/latest", + "data": { + "id": 1, + "title": "Latest Revision", + "url": "my-url" + }, + "status_code": 200 + }, + "get_latest_rev_by_id": { + "method": "GET", + "endpoint": "courses/1/pages/my-url/revisions/2", + "data": { + "id": 1, + "updated_at": "2012-08-07T11:23:58-06:00", + "revision_id": 2, + "url": "my-url" + }, + "status_code": 200 + }, + "get_latest_rev_by_id_group": { + "method": "GET", + "endpoint": "groups/1/pages/my-url/revisions/2", + "data": { + "id": 1, + "revision_id": 2, + "url": "my-url" + }, + "status_code": 200 + }, + "revert_to_revision": { + "method": "POST", + "endpoint": "courses/1/pages/my-url/revisions/3", + "data": { + "id": 1, + "revision_id": 3, + "url": "my-url" + }, + "status_code": 200 + }, + "revert_to_revision_group": { + "method": "POST", + "endpoint": "groups/1/pages/my-url/revisions/3", + "data": { + "id": 1, + "revision_id": 3, + "url": "my-url" + }, + "status_code": 200 + } +} \ No newline at end of file diff --git a/tests/fixtures/paginated_list.json b/tests/fixtures/paginated_list.json index a468e334..904906d2 100644 --- a/tests/fixtures/paginated_list.json +++ b/tests/fixtures/paginated_list.json @@ -45,7 +45,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -78,7 +78,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -96,7 +96,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, diff --git a/tests/fixtures/progress.json b/tests/fixtures/progress.json new file mode 100644 index 00000000..8642747f --- /dev/null +++ b/tests/fixtures/progress.json @@ -0,0 +1,20 @@ +{ + "progress_query": { + "method": "GET", + "endpoint": "progress/2", + "data": { + "id": 2, + "context_id": 1, + "context_type": "Account", + "user_id": 123, + "tag": "assign_unassigned_members", + "completion": 100, + "workflow_state": "running", + "created_at": "2013-01-15T15:00:00Z", + "updated_at": "2013-01-15T15:04:00Z", + "message": "17 courses processed", + "url": "https://canvas.example.edu/api/v1/progress/1" + }, + "status_code": 200 + } +} \ No newline at end of file diff --git a/tests/fixtures/requests.json b/tests/fixtures/requests.json index 2c9354ed..12a04ce6 100644 --- a/tests/fixtures/requests.json +++ b/tests/fixtures/requests.json @@ -5,9 +5,18 @@ "data": {}, "status_code": 400 }, - "401": { + "401_invalid_access_token": { "method": "ANY", - "endpoint": "401", + "endpoint": "401_invalid_access_token", + "data": {}, + "headers": { + "WWW-Authenticate": "Bearer realm=\"canvas-lms\"" + }, + "status_code": 401 + }, + "401_unauthorized": { + "method": "ANY", + "endpoint": "401_unauthorized", "data": {}, "status_code": 401 }, diff --git a/tests/fixtures/section.json b/tests/fixtures/section.json index 141db2a2..93d0fb9b 100644 --- a/tests/fixtures/section.json +++ b/tests/fixtures/section.json @@ -26,7 +26,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "list_enrollments_2": { @@ -45,5 +45,42 @@ } ], "status_code": 200 + }, + "crosslist_section":{ + "method": "POST", + "endpoint": "sections/1/crosslist/2", + "data": { + "id": 1, + "new_course_id": 2, + "name": "Cross Section" + }, + "status_code": 200 + }, + "decross_section": { + "method": "DELETE", + "endpoint": "sections/1/crosslist", + "data": { + "id": 1, + "name": "Target Section" + }, + "status_code": 200 + }, + "edit": { + "method": "PUT", + "endpoint": "sections/1", + "data": { + "id": 1, + "name": "Target Edit" + }, + "status_code": 200 + }, + "delete": { + "method": "DELETE", + "endpoint": "sections/1", + "data": { + "id": 1, + "name": "Deleted Section" + }, + "status_code": 200 } } \ No newline at end of file diff --git a/tests/fixtures/uploader.json b/tests/fixtures/uploader.json index 08e8179e..efcdb107 100644 --- a/tests/fixtures/uploader.json +++ b/tests/fixtures/uploader.json @@ -3,7 +3,7 @@ "method": "POST", "endpoint": "upload_response", "data": { - "upload_url": "mock://example.com/api/v1/upload_response_upload_url", + "upload_url": "http://example.com/api/v1/upload_response_upload_url", "upload_params": { "some_param": "param123", "a_different_param": "param456" @@ -35,7 +35,7 @@ "method": "POST", "endpoint": "upload_response_fail", "data": { - "upload_url": "mock://example.com/api/v1/upload_fail", + "upload_url": "http://example.com/api/v1/upload_fail", "upload_params": { "some_param": "param123", "a_different_param": "param456" diff --git a/tests/fixtures/user.json b/tests/fixtures/user.json index 1754dafa..ccbbc139 100644 --- a/tests/fixtures/user.json +++ b/tests/fixtures/user.json @@ -32,7 +32,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -117,7 +117,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "course_nicknames_delete": { @@ -161,7 +161,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -196,7 +196,8 @@ "data": { "id": 1, "name": "John Doe" - } + }, + "status_code": 200 }, "get_by_id_2": { "method": "GET", @@ -233,7 +234,7 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" } }, "get_user_assignments2": { @@ -270,8 +271,8 @@ ], "status_code": 200, "headers": { - "Link": "; rel=\"next\"" - } + "Link": "; rel=\"next\"" + } }, "list_enrollments_2": { "method": "GET", @@ -288,6 +289,39 @@ ], "status_code": 200 }, + "list_groups": { + "method": "GET", + "endpoint": "users/self/groups", + "data": [ + { + "id": 1, + "name": "Group 1" + }, + { + "id": 2, + "name": "Group 2" + } + ], + "status_code": 200, + "headers": { + "Link": "; rel=\"next\"" + } + }, + "list_groups2": { + "method": "GET", + "endpoint": "users/self/groups?page=2&per_page=2", + "data": [ + { + "id": 3, + "name": "Group 3" + }, + { + "id": 4, + "name": "Group 4" + } + ], + "status_code": 200 + }, "merge": { "method": "PUT", "endpoint": "users/1/merge_into/2", @@ -312,7 +346,7 @@ } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -339,17 +373,19 @@ "data": [ { "id": "123fb561-c9ce-7ed9-8e42-31e0aab47e18", - "url": "mock://example.com/test", - "created_at": "2013-01-01T12:00:00Z" + "url": "http://example.com/test", + "created_at": "2013-01-01T12:00:00Z", + "context_type": "Course" }, { "id": "4c7f44f9-41b9-ff59-03ad-3215cb246966", - "url": "mock://example.com/hello", - "created_at": "2014-01-01T12:00:00Z" + "url": "http://example.com/hello", + "created_at": "2014-01-01T12:00:00Z", + "context_type": "User" } ], "headers": { - "Link": "; rel=\"next\"" + "Link": "; rel=\"next\"" }, "status_code": 200 }, @@ -359,13 +395,15 @@ "data": [ { "id": "77f111c9-5b0b-46cb-92a2-34108ebe7b5b", - "url": "mock://example.com/login", - "created_at": "2015-01-01T12:00:00Z" + "url": "http://example.com/login", + "created_at": "2015-01-01T12:00:00Z", + "context_type": null }, { "id": "69ec8b6f-e123-2af8-dafb-561fdc711f6b", - "url": "mock://example.com/logout", - "created_at": "2016-01-01T12:00:00Z" + "url": "http://example.com/logout", + "created_at": "2016-01-01T12:00:00Z", + "context_type": "UserProfile" } ], "status_code": 200 @@ -438,18 +476,18 @@ "method": "POST", "endpoint": "users/1/files", "data": { - "upload_url": "mock://example.com/api/v1/files/upload_response_upload_url", + "upload_url": "http://example.com/api/v1/files/upload_response_upload_url", "upload_params": { - "some_param": "param123", - "a_different_param": "param456" + "some_param": "param123", + "a_different_param": "param456" } } }, "upload_final": { "method": "POST", - "endpoint": "files/upload_response_upload_url", - "data": { - "url": "great_url_success" - } + "endpoint": "files/upload_response_upload_url", + "data": { + "url": "great_url_success" + } } } diff --git a/tests/settings.py b/tests/settings.py index 4ec0b13f..636763d8 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,4 +1,4 @@ -BASE_URL = 'mock://example.com/api/v1/' +BASE_URL = 'http://example.com/api/v1/' API_KEY = '123' INVALID_ID = 9001 diff --git a/tests/test_account.py b/tests/test_account.py index dd107a64..7e51c315 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -3,53 +3,41 @@ import requests_mock -import settings from pycanvas import Canvas -from pycanvas.account import Account, AccountNotification, AccountReport +from pycanvas.account import Account, AccountNotification, AccountReport, Role from pycanvas.course import Course from pycanvas.enrollment import Enrollment from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import RequiredFieldMissing +from pycanvas.group import Group, GroupCategory from pycanvas.user import User -from util import register_uris +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestAccount(unittest.TestCase): - """ - Tests Account methods. - """ + @classmethod - def setUpClass(self): - requires = { - 'account': [ - 'close_notification', 'create', 'create_2', 'create_course', - 'create_notification', 'create_subaccount', 'create_user', - 'delete_user', 'enroll_by_id', 'get_by_id', 'get_by_id_2', - 'get_by_id_3', 'get_courses', 'get_courses_page_2', - 'get_external_tools', 'get_external_tools_p2', 'reports', - 'reports_page_2', 'report_index', 'report_index_page_2', - 'subaccounts', 'subaccounts_page_2', 'users', 'users_page_2', - 'user_notifs', 'user_notifs_page_2', 'update', 'update_fail' - ], - 'external_tool': ['get_by_id_account'], - 'user': ['get_by_id'], - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + requires = {'account': ['get_by_id'], 'user': ['get_by_id']} + register_uris(requires, m) - self.account = self.canvas.get_account(1) - self.user = self.canvas.get_user(1) + self.account = self.canvas.get_account(1) + self.user = self.canvas.get_user(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.account) assert isinstance(string, str) # close_notification_for_user() - def test_close_notification_for_user_id(self): + def test_close_notification_for_user_id(self, m): + register_uris({'account': ['close_notification']}, m) + user_id = self.user.id notif_id = 1 closed_notif = self.account.close_notification_for_user(user_id, notif_id) @@ -57,26 +45,34 @@ def test_close_notification_for_user_id(self): assert isinstance(closed_notif, AccountNotification) assert hasattr(closed_notif, 'subject') - def test_close_notification_for_user_obj(self): + def test_close_notification_for_user_obj(self, m): + register_uris({'account': ['close_notification']}, m) + notif_id = 1 self.account.close_notification_for_user(self.user, notif_id) # create_account() - def test_create_account(self): + def test_create_account(self, m): + register_uris({'account': ['create_2']}, m) + new_account = self.account.create_account() assert isinstance(new_account, Account) assert hasattr(new_account, 'id') # create_course() - def test_create_course(self): + def test_create_course(self, m): + register_uris({'account': ['create_course']}, m) + course = self.account.create_course() assert isinstance(course, Course) assert hasattr(course, 'name') # create_subaccount() - def test_create_subaccount(self): + def test_create_subaccount(self, m): + register_uris({'account': ['create_subaccount']}, m) + subaccount_name = "New Subaccount" subaccount = self.account.create_subaccount({'name': subaccount_name}) @@ -86,12 +82,14 @@ def test_create_subaccount(self): assert hasattr(subaccount, 'root_account_id') assert subaccount.root_account_id == self.account.id - def test_create_course_missing_field(self): + def test_create_course_missing_field(self, m): with self.assertRaises(RequiredFieldMissing): self.account.create_subaccount({}) # create_user() - def test_create_user(self): + def test_create_user(self, m): + register_uris({'account': ['create_user']}, m) + unique_id = 123456 user = self.account.create_user({'unique_id': unique_id}) @@ -99,12 +97,14 @@ def test_create_user(self): assert hasattr(user, 'unique_id') assert user.unique_id == unique_id - def test_create_user_missing_field(self): + def test_create_user_missing_field(self, m): with self.assertRaises(RequiredFieldMissing): self.account.create_user({}) # create_notification() - def test_create_notification(self): + def test_create_notification(self, m): + register_uris({'account': ['create_notification']}, m) + subject = 'Subject' notif_dict = { 'subject': subject, @@ -120,25 +120,32 @@ def test_create_notification(self): assert hasattr(notif, 'start_at_date') assert isinstance(notif.start_at_date, datetime.datetime) - def test_create_notification_missing_field(self): + def test_create_notification_missing_field(self, m): with self.assertRaises(RequiredFieldMissing): self.account.create_notification({}) # delete_user() - def test_delete_user_id(self): + def test_delete_user_id(self, m): + register_uris({'account': ['delete_user']}, m) + deleted_user = self.account.delete_user(self.user.id) assert isinstance(deleted_user, User) assert hasattr(deleted_user, 'name') - def test_delete_user_obj(self): + def test_delete_user_obj(self, m): + register_uris({'account': ['delete_user']}, m) + deleted_user = self.account.delete_user(self.user) assert isinstance(deleted_user, User) assert hasattr(deleted_user, 'name') # get_courses() - def test_get_courses(self): + def test_get_courses(self, m): + required = {'account': ['get_courses', 'get_courses_page_2']} + register_uris(required, m) + courses = self.account.get_courses() course_list = [course for course in courses] @@ -147,14 +154,20 @@ def test_get_courses(self): assert hasattr(course_list[0], 'name') # get_external_tool() - def test_get_external_tool(self): + def test_get_external_tool(self, m): + required = {'external_tool': ['get_by_id_account']} + register_uris(required, m) + tool = self.account.get_external_tool(1) assert isinstance(tool, ExternalTool) assert hasattr(tool, 'name') # get_external_tools() - def test_get_external_tools(self): + def test_get_external_tools(self, m): + required = {'account': ['get_external_tools', 'get_external_tools_p2']} + register_uris(required, m) + tools = self.account.get_external_tools() tool_list = [tool for tool in tools] @@ -162,7 +175,10 @@ def test_get_external_tools(self): assert len(tool_list) == 4 # get_index_of_reports() - def test_get_index_of_reports(self): + def test_get_index_of_reports(self, m): + required = {'account': ['report_index', 'report_index_page_2']} + register_uris(required, m) + reports_index = self.account.get_index_of_reports("sis_export_csv") reports_index_list = [index for index in reports_index] @@ -171,7 +187,10 @@ def test_get_index_of_reports(self): assert hasattr(reports_index_list[0], 'id') # get_reports() - def test_get_reports(self): + def test_get_reports(self, m): + required = {'account': ['reports', 'reports_page_2']} + register_uris(required, m) + reports = self.account.get_reports() reports_list = [report for report in reports] @@ -180,7 +199,10 @@ def test_get_reports(self): assert hasattr(reports_list[0], 'id') # get_subaccounts() - def test_get_subaccounts(self): + def test_get_subaccounts(self, m): + required = {'account': ['subaccounts', 'subaccounts_page_2']} + register_uris(required, m) + subaccounts = self.account.get_subaccounts() subaccounts_list = [account for account in subaccounts] @@ -189,7 +211,10 @@ def test_get_subaccounts(self): assert hasattr(subaccounts_list[0], 'name') # get_users() - def test_get_users(self): + def test_get_users(self, m): + required = {'account': ['users', 'users_page_2']} + register_uris(required, m) + users = self.account.get_users() user_list = [user for user in users] @@ -198,7 +223,10 @@ def test_get_users(self): assert hasattr(user_list[0], 'name') # get_user_notifications() - def test_get_user_notifications_id(self): + def test_get_user_notifications_id(self, m): + required = {'account': ['user_notifs', 'user_notifs_page_2']} + register_uris(required, m) + user_notifs = self.account.get_user_notifications(self.user.id) notif_list = [notif for notif in user_notifs] @@ -206,7 +234,10 @@ def test_get_user_notifications_id(self): assert isinstance(user_notifs[0], AccountNotification) assert hasattr(user_notifs[0], 'subject') - def test_get_user_notifications_obj(self): + def test_get_user_notifications_obj(self, m): + required = {'account': ['user_notifs', 'user_notifs_page_2']} + register_uris(required, m) + user_notifs = self.account.get_user_notifications(self.user) notif_list = [notif for notif in user_notifs] @@ -215,30 +246,114 @@ def test_get_user_notifications_obj(self): assert hasattr(user_notifs[0], 'subject') # update() - def test_update(self): - account = self.canvas.get_account(100) - assert account.name == 'Old Name' + def test_update(self, m): + register_uris({'account': ['update']}, m) + + self.assertEqual(self.account.name, 'Canvas Account') new_name = 'Updated Name' update_account_dict = {'name': new_name} - success = account.update(account=update_account_dict) + self.assertTrue(self.account.update(account=update_account_dict)) + self.assertEqual(self.account.name, new_name) - assert success - assert account.name == new_name + def test_update_fail(self, m): + register_uris({'account': ['update_fail']}, m) - def test_update_fail(self): - account = self.canvas.get_account(101) - assert account.name == 'Old Name' + self.assertEqual(self.account.name, 'Canvas Account') new_name = 'Updated Name' update_account_dict = {'name': new_name} - success = account.update(account=update_account_dict) - assert not success + self.assertFalse(self.account.update(account=update_account_dict)) + + def test_list_roles(self, m): + requires = {'account': ['list_roles', 'list_roles_2']} + register_uris(requires, m) + + roles = self.account.list_roles() + role_list = [role for role in roles] + + assert len(role_list) == 4 + assert isinstance(role_list[0], Role) + assert hasattr(role_list[0], 'role') + assert hasattr(role_list[0], 'label') - # enroll_by_id() - def test_enroll_by_id(self): - target_enrollment = self.account.enroll_by_id(1) + def test_get_role(self, m): + register_uris({'account': ['get_role']}, m) + + target_role = self.account.get_role(2) + + assert isinstance(target_role, Role) + assert hasattr(target_role, 'role') + assert hasattr(target_role, 'label') + + def test_create_role(self, m): + register_uris({'account': ['create_role']}, m) + + new_role = self.account.create_role(1) + + assert isinstance(new_role, Role) + assert hasattr(new_role, 'role') + assert hasattr(new_role, 'label') + + def test_deactivate_role(self, m): + register_uris({'account': ['deactivate_role']}, m) + + old_role = self.account.deactivate_role(2) + + assert isinstance(old_role, Role) + assert hasattr(old_role, 'role') + assert hasattr(old_role, 'label') + + def test_activate_role(self, m): + register_uris({'account': ['activate_role']}, m) + + activated_role = self.account.activate_role(2) + + assert isinstance(activated_role, Role) + assert hasattr(activated_role, 'role') + assert hasattr(activated_role, 'label') + + def test_update_role(self, m): + register_uris({'account': ['update_role']}, m) + + updated_role = self.account.update_role(2) + + assert isinstance(updated_role, Role) + assert hasattr(updated_role, 'role') + assert hasattr(updated_role, 'label') + + # get_enrollment() + def test_get_enrollment(self, m): + register_uris({'enrollment': ['get_by_id']}, m) + + target_enrollment = self.account.get_enrollment(1) assert isinstance(target_enrollment, Enrollment) + + def test_list_groups(self, m): + requires = {'account': ['list_groups_context', 'list_groups_context2']} + register_uris(requires, m) + + groups = self.account.list_groups() + group_list = [group for group in groups] + + assert isinstance(group_list[0], Group) + assert len(group_list) == 4 + + # create_group_category() + def test_create_group_category(self, m): + register_uris({'account': ['create_group_category']}, m) + + name_str = "Test String" + response = self.account.create_group_category(name=name_str) + assert isinstance(response, GroupCategory) + + # list_group_categories() + def test_list_group_categories(self, m): + register_uris({'account': ['list_group_categories']}, m) + + response = self.account.list_group_categories() + category_list = [category for category in response] + assert isinstance(category_list[0], GroupCategory) diff --git a/tests/test_assignment.py b/tests/test_assignment.py index c6adb71e..cdac2984 100644 --- a/tests/test_assignment.py +++ b/tests/test_assignment.py @@ -1,50 +1,46 @@ import unittest + import requests_mock -import settings from pycanvas import Canvas from pycanvas.assignment import Assignment -from util import register_uris +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestAssignment(unittest.TestCase): - """ - Tests Assignment functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'assignment': ['edit_assignment', 'delete_assignment'], - 'course': ['get_by_id', 'get_assignment_by_id'], - 'user': ['get_by_id'] - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id', 'get_assignment_by_id']}, m) - self.course = self.canvas.get_course(1) + self.course = self.canvas.get_course(1) + self.assignment = self.course.get_assignment(5) # edit() - def test_edit_assignment(self): + def test_edit_assignment(self, m): + register_uris({'assignment': ['edit_assignment']}, m) + name = 'New Name' - assignment = self.course.get_assignment(5) - edited_assignment = assignment.edit(assignment={'name': name}) + edited_assignment = self.assignment.edit(assignment={'name': name}) assert isinstance(edited_assignment, Assignment) assert hasattr(edited_assignment, 'name') assert edited_assignment.name == name # delete() - def test_delete_assignments(self): - - assignment = self.course.get_assignment('5') + def test_delete_assignments(self, m): + register_uris({'assignment': ['delete_assignment']}, m) - deleted_assignment = assignment.delete() + deleted_assignment = self.assignment.delete() assert isinstance(deleted_assignment, Assignment) # __str__() - def test__str__(self): - string = str(self.course.get_assignment('5')) + def test__str__(self, m): + string = str(self.assignment) assert isinstance(string, str) diff --git a/tests/test_canvas.py b/tests/test_canvas.py index 446408a8..3d673768 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -3,52 +3,30 @@ import requests_mock -import settings from pycanvas import Canvas from pycanvas.account import Account +from pycanvas.conversation import Conversation from pycanvas.course import Course, CourseNickname -from pycanvas.group import Group +from pycanvas.group import Group, GroupCategory from pycanvas.exceptions import ResourceDoesNotExist +from pycanvas.progress import Progress from pycanvas.section import Section from pycanvas.user import User -from util import register_uris +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestCanvas(unittest.TestCase): - """ - Test core Canvas functionality. - """ - @classmethod - def setUpClass(self): - requires = { - 'account': [ - 'create', 'domains', 'get_by_id', 'multiple', 'multiple_course' - ], - 'course': [ - 'get_by_id', 'multiple', 'multiple_page_2', 'start_at_date', - 'unicode_encode_error' - ], - 'group': ['get_single_group'], - 'section': ['get_by_id'], - 'user': [ - 'activity_stream_summary', 'course_nickname', 'course_nickname_set', - 'course_nicknames', 'course_nicknames_delete', - 'course_nicknames_page_2', 'courses', 'courses_p2', 'get_by_id', - 'get_by_id_type', 'todo_items', 'upcoming_events' - ], - } - require_generic = { - 'generic': ['not_found'] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, require_generic, adapter) - register_uris(settings.BASE_URL, requires, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) # create_account() - def test_create_account(self): + def test_create_account(self, m): + register_uris({'account': ['create']}, m) + name = 'Newly Created Account' account_dict = { @@ -61,35 +39,47 @@ def test_create_account(self): assert account.name == name # get_account() - def test_get_account(self): + def test_get_account(self, m): + register_uris({'account': ['get_by_id']}, m) + account = self.canvas.get_account(1) assert isinstance(account, Account) - def test_get_account_fail(self): + def test_get_account_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_account(settings.INVALID_ID) # get_accounts() - def test_get_accounts(self): + def test_get_accounts(self, m): + register_uris({'account': ['multiple']}, m) + accounts = self.canvas.get_accounts() account_list = [account for account in accounts] assert len(account_list) == 2 # get_course_accounts() - def test_get_course_accounts(self): + def test_get_course_accounts(self, m): + register_uris({'account': ['multiple_course']}, m) + accounts = self.canvas.get_course_accounts() account_list = [account for account in accounts] assert len(account_list) == 2 # get_course() - def test_get_course(self): + def test_get_course(self, m): + register_uris({'course': ['get_by_id']}, m) + course = self.canvas.get_course(1) assert isinstance(course, Course) assert hasattr(course, 'name') - def test_get_course_with_start_date(self): + def test_get_course_with_start_date(self, m): + register_uris({'course': ['start_at_date']}, m) + course = self.canvas.get_course(2) assert hasattr(course, 'start_at') @@ -97,34 +87,46 @@ def test_get_course_with_start_date(self): assert hasattr(course, 'start_at_date') assert isinstance(course.start_at_date, datetime) - def test_get_course_non_unicode_char(self): + def test_get_course_non_unicode_char(self, m): + register_uris({'course': ['unicode_encode_error']}, m) + course = self.canvas.get_course(3) assert hasattr(course, 'name') - def test_get_course_fail(self): + def test_get_course_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_course(settings.INVALID_ID) # get_user() - def test_get_user(self): + def test_get_user(self, m): + register_uris({'user': ['get_by_id']}, m) + user = self.canvas.get_user(1) assert isinstance(user, User) assert hasattr(user, 'name') - def test_get_user_by_id_type(self): + def test_get_user_by_id_type(self, m): + register_uris({'user': ['get_by_id_type']}, m) + user = self.canvas.get_user('jdoe', 'sis_user_id') assert isinstance(user, User) assert hasattr(user, 'name') - def test_get_user_fail(self): + def test_get_user_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_user(settings.INVALID_ID) # get_courses() - def test_get_courses(self): + def test_get_courses(self, m): + register_uris({'course': ['multiple', 'multiple_page_2']}, m) + courses = self.canvas.get_courses(per_page=1) course_list = [course for course in courses] @@ -132,25 +134,33 @@ def test_get_courses(self): assert isinstance(course_list[0], Course) # get_activity_stream_summary() - def test_get_activity_stream_summary(self): + def test_get_activity_stream_summary(self, m): + register_uris({'user': ['activity_stream_summary']}, m) + summary = self.canvas.get_activity_stream_summary() assert isinstance(summary, list) # get_todo_items() - def test_get_todo_items(self): + def test_get_todo_items(self, m): + register_uris({'user': ['todo_items']}, m) + todo_items = self.canvas.get_todo_items() assert isinstance(todo_items, list) # get_upcoming_events() - def test_get_upcoming_events(self): + def test_get_upcoming_events(self, m): + register_uris({'user': ['upcoming_events']}, m) + events = self.canvas.get_upcoming_events() assert isinstance(events, list) # get_course_nicknames() - def test_get_course_nicknames(self): + def test_get_course_nicknames(self, m): + register_uris({'user': ['course_nicknames', 'course_nicknames_page_2']}, m) + nicknames = self.canvas.get_course_nicknames() nickname_list = [name for name in nicknames] @@ -159,18 +169,24 @@ def test_get_course_nicknames(self): assert hasattr(nickname_list[0], 'nickname') # get_course_nickname() - def test_get_course_nickname(self): + def test_get_course_nickname(self, m): + register_uris({'user': ['course_nickname']}, m) + nickname = self.canvas.get_course_nickname(1) assert isinstance(nickname, CourseNickname) assert hasattr(nickname, 'nickname') - def test_get_course_nickname_fail(self): + def test_get_course_nickname_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.canvas.get_course_nickname(settings.INVALID_ID) # set_course_nickname() - def test_set_course_nickname(self): + def test_set_course_nickname(self, m): + register_uris({'user': ['course_nickname_set']}, m) + name = 'New Course Nickname' nickname = self.canvas.set_course_nickname(1, name) @@ -180,12 +196,16 @@ def test_set_course_nickname(self): assert nickname.nickname == name # clear_course_nicknames() - def test_clear_course_nicknames(self): + def test_clear_course_nicknames(self, m): + register_uris({'user': ['course_nicknames_delete']}, m) + success = self.canvas.clear_course_nicknames() assert success # search_accounts() - def test_search_accounts(self): + def test_search_accounts(self, m): + register_uris({'account': ['domains']}, m) + domains = self.canvas.search_accounts() assert isinstance(domains, list) @@ -193,15 +213,124 @@ def test_search_accounts(self): assert 'name' in domains[0] # get_section() - def test_section(self): + def test_get_section(self, m): + register_uris({'section': ['get_by_id']}, m) + info = self.canvas.get_section(1) assert isinstance(info, Section) + # create_group() + def test_create_group(self, m): + register_uris({'group': ['create']}, m) + + group = self.canvas.create_group() + + assert isinstance(group, Group) + assert hasattr(group, 'name') + assert hasattr(group, 'description') + # get_group() - def test_get_group(self): + def test_get_group(self, m): + register_uris({'group': ['get_by_id']}, m) + group = self.canvas.get_group(1) assert isinstance(group, Group) assert hasattr(group, 'name') assert hasattr(group, 'description') + + # get_group_category() + def test_get_group_category(self, m): + register_uris({'group': ['get_category_by_id']}, m) + + response = self.canvas.get_group_category(1) + assert isinstance(response, GroupCategory) + + # create_conversation() + def test_create_conversation(self, m): + register_uris({'conversation': ['create_conversation']}, m) + + recipients = ['1', '2'] + body = 'Test Conversation Body' + + conversations = self.canvas.create_conversation(recipients=recipients, body=body) + conversation_list = [conversation for conversation in conversations] + + assert isinstance(conversation_list[0], Conversation) + assert len(conversation_list) == 2 + + # get_conversation() + def test_get_conversation(self, m): + register_uris({'conversation': ['get_by_id']}, m) + + convo = self.canvas.get_conversation(1) + + assert isinstance(convo, Conversation) + assert hasattr(convo, 'subject') + + # get_conversations() + def test_get_conversations(self, m): + requires = { + 'conversation': ['get_conversations', 'get_conversations_2'] + } + register_uris(requires, m) + + convos = self.canvas.get_conversations() + conversation_list = [conversation for conversation in convos] + + assert len(conversation_list) == 4 + assert isinstance(conversation_list[0], Conversation) + + # mark_all_as_read() + def test_conversations_mark_all_as_read(self, m): + register_uris({'conversation': ['mark_all_as_read']}, m) + + result = self.canvas.conversations_mark_all_as_read() + assert result is True + + # unread_count() + def test_conversations_unread_count(self, m): + register_uris({'conversation': ['unread_count']}, m) + + result = self.canvas.conversations_unread_count() + assert result['unread_count'] == "7" + + # get_running_batches() + def test_conversations_get_running_batches(self, m): + register_uris({'conversation': ['get_running_batches']}, m) + + result = self.canvas.conversations_get_running_batches() + assert len(result) == 2 + assert 'body' in result[0]['message'] + assert result[1]['message']['author_id'] == 1 + + # batch_update() + def test_conversations_batch_update(self, m): + register_uris({'conversation': ['batch_update']}, m) + + conversation_ids = [1, 2] + this_event = "mark_as_read" + result = self.canvas.conversations_batch_update( + event=this_event, + conversation_ids=conversation_ids + ) + assert isinstance(result, Progress) + + def test_conversations_batch_updated_fail_on_event(self, m): + conversation_ids = [1, 2] + this_event = "this doesn't work" + result = self.canvas.conversations_batch_update( + event=this_event, + conversation_ids=conversation_ids + ) + assert isinstance(result, ValueError) + + def test_conversations_batch_updated_fail_on_ids(self, m): + conversation_ids = [None] * 501 + this_event = "mark_as_read" + result = self.canvas.conversations_batch_update( + event=this_event, + conversation_ids=conversation_ids + ) + assert isinstance(result, ValueError) diff --git a/tests/test_canvas_object.py b/tests/test_canvas_object.py index 058b497a..1bf0e8c6 100644 --- a/tests/test_canvas_object.py +++ b/tests/test_canvas_object.py @@ -4,9 +4,6 @@ class TestCanvasObject(unittest.TestCase): - """ - Test CanvasObject functionality. - """ # to_json() def test_canvas_object_to_json(self): diff --git a/tests/test_conversation.py b/tests/test_conversation.py new file mode 100644 index 00000000..6d18b97e --- /dev/null +++ b/tests/test_conversation.py @@ -0,0 +1,87 @@ +import unittest + +import requests_mock + +from pycanvas import Canvas +from pycanvas.conversation import Conversation +from tests import settings +from tests.util import register_uris + + +@requests_mock.Mocker() +class TestConversation(unittest.TestCase): + + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris({'conversation': ['get_by_id']}, m) + + self.conversation = self.canvas.get_conversation(1) + + # __str__() + def test__str__(self, m): + string = str(self.conversation) + assert isinstance(string, str) + + # edit() + def test_edit(self, m): + register_uris({'conversation': ['edit_conversation']}, m) + + new_subject = "conversations api example" + success = self.conversation.edit(subject=new_subject) + assert success + + def test_edit_fail(self, m): + requires = {'conversation': ['get_by_id_2', 'edit_conversation_fail']} + register_uris(requires, m) + + temp_convo = self.canvas.get_conversation(2) + assert temp_convo.edit() is False + + # delete() + def test_delete(self, m): + register_uris({'conversation': ['delete_conversation']}, m) + + success = self.conversation.delete() + assert success + + def test_delete_fail(self, m): + requires = {'conversation': ['get_by_id_2', 'delete_conversation_fail']} + register_uris(requires, m) + + temp_convo = self.canvas.get_conversation(2) + assert temp_convo.delete() is False + + # add_recipients() + def test_add_recipients(self, m): + register_uris({'conversation': ['add_recipients']}, m) + + recipients = {'bob': 1, 'joe': 2} + string_bob = "Bob was added to the conversation by Hank TA" + string_joe = "Joe was added to the conversation by Hank TA" + result = self.conversation.add_recipients([recipients['bob'], recipients['joe']]) + assert hasattr(result, 'messages') + assert len(result.messages) == 2 + assert result.messages[0]["body"] == string_bob + assert result.messages[1]["body"] == string_joe + + # add_message() + def test_add_message(self, m): + register_uris({'conversation': ['add_message']}, m) + + test_string = "add_message test body" + result = self.conversation.add_message(test_string) + assert isinstance(result, Conversation) + assert len(result.messages) == 1 + assert result.messages[0]['id'] == 3 + + # delete_message() + def test_delete_message(self, m): + register_uris({'conversation': ['delete_message']}, m) + + id_list = [1] + result = self.conversation.delete_messages(id_list) + assert 'subject' in result + assert result['id'] == 1 diff --git a/tests/test_course.py b/tests/test_course.py index a90f0c9b..79e4f0a5 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -1,116 +1,92 @@ import unittest import uuid -import requests import os import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.assignment import Assignment from pycanvas.course import Course, CourseNickname, Page from pycanvas.enrollment import Enrollment -from pycanvas.external_tool import ExternalTool from pycanvas.exceptions import ResourceDoesNotExist, RequiredFieldMissing +from pycanvas.external_tool import ExternalTool +from pycanvas.group import Group, GroupCategory from pycanvas.module import Module from pycanvas.quiz import Quiz from pycanvas.section import Section from pycanvas.user import User +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestCourse(unittest.TestCase): @classmethod - def setUpClass(self): - requires = { - 'course': [ - 'create', 'create_assignment', 'create_module', 'create_page', - 'deactivate_enrollment', 'edit_front_page', 'enroll_user', - 'get_all_assignments', 'get_all_assignments2', - 'get_assignment_by_id', 'get_by_id', 'get_external_tools', - 'get_external_tools_p2', 'get_module_by_id', 'get_page', - 'get_pages', 'get_pages2', 'get_quiz', 'get_recent_students', - 'get_recent_students_p2', 'get_section', 'get_user', - 'get_user_id_type', 'get_users', 'get_users_p2', - 'list_enrollments', 'list_enrollments_2', 'list_modules', - 'list_modules2', 'list_quizzes', 'list_quizzes2', - 'preview_html', 'reactivate_enrollment', 'reset', 'settings', - 'show_front_page', 'update', 'update_settings', 'upload', - 'upload_final' - ], - 'external_tool': ['get_by_id_course'], - 'quiz': ['get_by_id'], - 'user': ['get_by_id'], - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, {'generic': ['not_found']}, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - # define custom matchers - def conclude_matcher(request): - if (request.path_url == '/api/v1/courses/1' and request.body and - 'event=conclude' in request.body): - resp = requests.Response() - resp.status_code = 200 - resp._content = '{"conclude": true}' - return resp - - def delete_matcher(request): - if (request.path_url == '/api/v1/courses/1' and request.body and - 'event=delete' in request.body): - resp = requests.Response() - resp.status_code = 200 - resp._content = '{"delete": true}' - return resp - - # register custom matchers - adapter.add_matcher(conclude_matcher) - adapter.add_matcher(delete_matcher) - - self.course = self.canvas.get_course(1) - self.page = self.course.get_page('my-url') - self.quiz = self.course.get_quiz(1) - self.user = self.canvas.get_user(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'get_page'], + 'quiz': ['get_by_id'], + 'user': ['get_by_id'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.page = self.course.get_page('my-url') + self.quiz = self.course.get_quiz(1) + self.user = self.canvas.get_user(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.course) assert isinstance(string, str) # conclude() - def test_conclude(self): + def test_conclude(self, m): + register_uris({'course': ['conclude']}, m) + success = self.course.conclude() assert success # delete() - def test_delete(self): + def test_delete(self, m): + register_uris({'course': ['delete']}, m) + success = self.course.delete() assert success # update() - def test_update(self): + def test_update(self, m): + register_uris({'course': ['update']}, m) + new_name = 'New Name' self.course.update(course={'name': new_name}) assert self.course.name == new_name # get_user() - def test_get_user(self): + def test_get_user(self, m): + register_uris({'course': ['get_user']}, m) + user = self.course.get_user(1) assert isinstance(user, User) assert hasattr(user, 'name') - def test_get_user_id_type(self): - user = self.course.get_user("ab123456", "sis_login_id") + def test_get_user_id_type(self, m): + register_uris({'course': ['get_user_id_type']}, m) + + user = self.course.get_user("SISLOGIN", "sis_login_id") assert isinstance(user, User) assert hasattr(user, 'name') # get_users() - def test_get_users(self): + def test_get_users(self, m): + register_uris({'course': ['get_users', 'get_users_p2']}, m) + users = self.course.get_users() user_list = [user for user in users] @@ -118,7 +94,13 @@ def test_get_users(self): assert isinstance(user_list[0], User) # enroll_user() - def test_enroll_user(self): + def test_enroll_user(self, m): + requires = { + 'course': ['enroll_user'], + 'user': ['get_by_id'] + } + register_uris(requires, m) + enrollment_type = 'TeacherEnrollment' user = self.canvas.get_user(1) enrollment = self.course.enroll_user(user, enrollment_type) @@ -128,7 +110,10 @@ def test_enroll_user(self): assert enrollment.type == enrollment_type # get_recent_students() - def test_get_recent_students(self): + def test_get_recent_students(self, m): + recent = {'course': ['get_recent_students', 'get_recent_students_p2']} + register_uris(recent, m) + students = self.course.get_recent_students() student_list = [student for student in students] @@ -137,7 +122,9 @@ def test_get_recent_students(self): assert hasattr(student_list[0], 'name') # preview_html() - def test_preview_html(self): + def test_preview_html(self, m): + register_uris({'course': ['preview_html']}, m) + html_str = "

hello

" prev_html = self.course.preview_html(html_str) @@ -145,20 +132,26 @@ def test_preview_html(self): assert prev_html == "

hello

" # get_settings() - def test_get_settings(self): + def test_get_settings(self, m): + register_uris({'course': ['settings']}, m) + settings = self.course.get_settings() assert isinstance(settings, dict) # update_settings() - def test_update_settings(self): + def test_update_settings(self, m): + register_uris({'course': ['update_settings']}, m) + settings = self.course.update_settings() assert isinstance(settings, dict) assert settings['hide_final_grades'] is True # upload() - def test_upload(self): + def test_upload(self, m): + register_uris({'course': ['upload', 'upload_final']}, m) + filename = 'testfile_%s' % uuid.uuid4().hex file = open(filename, 'w+') @@ -176,14 +169,18 @@ def test_upload(self): pass # reset() - def test_reset(self): + def test_reset(self, m): + register_uris({'course': ['reset']}, m) + course = self.course.reset() assert isinstance(course, Course) assert hasattr(course, 'name') # create_quiz() - def test_create_quiz(self): + def test_create_quiz(self, m): + register_uris({'course': ['create_quiz']}, m) + title = 'Newer Title' new_quiz = self.course.create_quiz({'title': title}) @@ -193,24 +190,30 @@ def test_create_quiz(self): assert hasattr(new_quiz, 'course_id') assert new_quiz.course_id == self.course.id - def test_create_quiz_fail(self): + def test_create_quiz_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_quiz({}) # get_quiz() - def test_get_quiz(self): + def test_get_quiz(self, m): + register_uris({'course': ['get_quiz']}, m) + target_quiz = self.course.get_quiz(1) assert isinstance(target_quiz, Quiz) assert hasattr(target_quiz, 'course_id') assert target_quiz.course_id == self.course.id - def test_get_quiz_fail(self): + def test_get_quiz_fail(self, m): + register_uris({'generic': ['not_found']}, m) + with self.assertRaises(ResourceDoesNotExist): self.course.get_quiz(settings.INVALID_ID) # get_quizzes() - def test_get_quizzes(self): + def test_get_quizzes(self, m): + register_uris({'course': ['list_quizzes', 'list_quizzes2']}, m) + quizzes = self.course.get_quizzes() quiz_list = [quiz for quiz in quizzes] @@ -220,7 +223,9 @@ def test_get_quizzes(self): assert quiz_list[0].course_id == self.course.id # get_modules() - def test_get_modules(self): + def test_get_modules(self, m): + register_uris({'course': ['list_modules', 'list_modules2']}, m) + modules = self.course.get_modules() module_list = [module for module in modules] @@ -230,7 +235,9 @@ def test_get_modules(self): assert module_list[0].course_id == self.course.id # get_module() - def test_get_module(self): + def test_get_module(self, m): + register_uris({'course': ['get_module_by_id']}, m) + target_module = self.course.get_module(1) assert isinstance(target_module, Module) @@ -238,7 +245,9 @@ def test_get_module(self): assert target_module.course_id == self.course.id # create_module() - def test_create_module(self): + def test_create_module(self, m): + register_uris({'course': ['create_module']}, m) + name = 'Name' new_module = self.course.create_module(module={'name': name}) @@ -247,42 +256,32 @@ def test_create_module(self): assert hasattr(new_module, 'course_id') assert new_module.course_id == self.course.id - def test_create_module_fail(self): + def test_create_module_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_module(module={}) # get_enrollments() - def test_get_enrollments(self): + def test_get_enrollments(self, m): + register_uris({'course': ['list_enrollments', 'list_enrollments_2']}, m) + enrollments = self.course.get_enrollments() enrollment_list = [enrollment for enrollment in enrollments] assert len(enrollment_list) == 4 assert isinstance(enrollment_list[0], Enrollment) - # deactivate_enrollment() - def test_deactivate_enrollment(self): - target_enrollment = self.course.deactivate_enrollment(1, 'conclude') - - assert isinstance(target_enrollment, Enrollment) - - def test_deactivate_enrollment_invalid_task(self): - with self.assertRaises(ValueError): - self.course.deactivate_enrollment(1, 'finish') - - # reactivate_enrollment() - def test_reactivate_enrollment(self): - target_enrollment = self.course.reactivate_enrollment(1) - - assert isinstance(target_enrollment, Enrollment) - # get_section - def test_get_section(self): + def test_get_section(self, m): + register_uris({'course': ['get_section']}, m) + section = self.course.get_section(1) assert isinstance(section, Section) # create_assignment() - def test_create_assignment(self): + def test_create_assignment(self, m): + register_uris({'course': ['create_assignment']}, m) + name = 'Newly Created Assignment' assignment = self.course.create_assignment(assignment={'name': name}) @@ -292,19 +291,24 @@ def test_create_assignment(self): assert assignment.name == name assert assignment.id == 5 - def test_create_assignment_fail(self): + def test_create_assignment_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_assignment(assignment={}) # get_assignment() - def test_get_assignment(self): + def test_get_assignment(self, m): + register_uris({'course': ['get_assignment_by_id']}, m) + assignment = self.course.get_assignment('5') assert isinstance(assignment, Assignment) assert hasattr(assignment, 'name') # get_assignments() - def test_get_assignments(self): + def test_get_assignments(self, m): + requires = {'course': ['get_all_assignments', 'get_all_assignments2']} + register_uris(requires, m) + assignments = self.course.get_assignments() assignment_list = [assignment for assignment in assignments] @@ -312,7 +316,9 @@ def test_get_assignments(self): assert len(assignment_list) == 4 # show_front_page() - def test_show_front_page(self): + def test_show_front_page(self, m): + register_uris({'course': ['show_front_page']}, m) + front_page = self.course.show_front_page() assert isinstance(front_page, Page) @@ -320,7 +326,9 @@ def test_show_front_page(self): assert hasattr(front_page, 'title') # create_front_page() - def test_edit_front_page(self): + def test_edit_front_page(self, m): + register_uris({'course': ['edit_front_page']}, m) + new_front_page = self.course.edit_front_page() assert isinstance(new_front_page, Page) @@ -328,14 +336,18 @@ def test_edit_front_page(self): assert hasattr(new_front_page, 'title') # get_page() - def test_get_page(self): + def test_get_page(self, m): + register_uris({'course': ['get_page']}, m) + url = 'my-url' page = self.course.get_page(url) assert isinstance(page, Page) # get_pages() - def test_get_pages(self): + def test_get_pages(self, m): + register_uris({'course': ['get_pages', 'get_pages2']}, m) + pages = self.course.get_pages() page_list = [page for page in pages] @@ -345,7 +357,9 @@ def test_get_pages(self): assert page_list[0].course_id == self.course.id # create_page() - def test_create_page(self): + def test_create_page(self, m): + register_uris({'course': ['create_page']}, m) + title = "Newest Page" new_page = self.course.create_page(wiki_page={'title': title}) @@ -355,51 +369,93 @@ def test_create_page(self): assert hasattr(new_page, 'course_id') assert new_page.course_id == self.course.id - def test_create_page_fail(self): + def test_create_page_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.course.create_page(settings.INVALID_ID) # get_external_tool() - def test_get_external_tool(self): + def test_get_external_tool(self, m): + register_uris({'external_tool': ['get_by_id_course']}, m) + tool = self.course.get_external_tool(1) assert isinstance(tool, ExternalTool) assert hasattr(tool, 'name') # get_external_tools() - def test_get_external_tools(self): + def test_get_external_tools(self, m): + requires = {'course': ['get_external_tools', 'get_external_tools_p2']} + register_uris(requires, m) + tools = self.course.get_external_tools() tool_list = [tool for tool in tools] assert isinstance(tool_list[0], ExternalTool) assert len(tool_list) == 4 + def test_list_sections(self, m): + register_uris({'course': ['list_sections', 'list_sections2']}, m) + + sections = self.course.list_sections() + section_list = [sect for sect in sections] + + assert isinstance(section_list[0], Section) + assert len(section_list) == 4 + + def test_create_course_section(self, m): + register_uris({'course': ['create_section']}, m) + + section = self.course.create_course_section() + assert isinstance(section, Section) + + def test_list_groups(self, m): + requires = {'course': ['list_groups_context', 'list_groups_context2']} + register_uris(requires, m) + + groups = self.course.list_groups() + group_list = [group for group in groups] + + assert isinstance(group_list[0], Group) + assert len(group_list) == 4 + + # create_group_category() + def test_create_group_category(self, m): + register_uris({'course': ['create_group_category']}, m) + + name_str = "Test String" + response = self.course.create_group_category(name=name_str) + assert isinstance(response, GroupCategory) + + # list_group_categories() + def test_list_group_categories(self, m): + register_uris({'course': ['list_group_categories']}, m) + + response = self.course.list_group_categories() + category_list = [category for category in response] + assert isinstance(category_list[0], GroupCategory) + + +@requests_mock.Mocker() class TestCourseNickname(unittest.TestCase): - """ - Tests CourseNickname methods - """ - @classmethod - def setUpClass(self): - requires = { - 'course': [], - 'generic': ['not_found'], - 'user': ['course_nickname', 'remove_nickname'] - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - self.nickname = self.canvas.get_course_nickname(1) + with requests_mock.Mocker() as m: + register_uris({'user': ['course_nickname']}, m) + self.nickname = self.canvas.get_course_nickname(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.nickname) assert isinstance(string, str) # remove() - def test_remove(self): + def test_remove(self, m): + register_uris({'user': ['remove_nickname']}, m) + deleted_nick = self.nickname.remove() assert isinstance(deleted_nick, CourseNickname) diff --git a/tests/test_enrollment.py b/tests/test_enrollment.py new file mode 100644 index 00000000..635600cc --- /dev/null +++ b/tests/test_enrollment.py @@ -0,0 +1,51 @@ +import unittest + +import requests_mock + +from pycanvas.canvas import Canvas +from pycanvas.enrollment import Enrollment +from tests import settings +from tests.util import register_uris + + +@requests_mock.Mocker() +class TestEnrollment(unittest.TestCase): + + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'account': ['get_by_id'], + 'enrollment': ['get_by_id'] + } + register_uris(requires, m) + + self.account = self.canvas.get_account(1) + self.enrollment = self.account.get_enrollment(1) + + # __str__() + def test__str__(self, m): + string = str(self.enrollment) + self.assertIsInstance(string, str) + + # deactivate() + def test_deactivate(self, m): + register_uris({'enrollment': ['deactivate']}, m) + + target_enrollment = self.enrollment.deactivate('conclude') + + self.assertIsInstance(target_enrollment, Enrollment) + + def test_deactivate_invalid_task(self, m): + with self.assertRaises(ValueError): + self.enrollment.deactivate('finish') + + # reactivate() + def test_reactivate(self, m): + register_uris({'enrollment': ['reactivate']}, m) + + target_enrollment = self.enrollment.reactivate() + + self.assertIsInstance(target_enrollment, Enrollment) diff --git a/tests/test_external_tool.py b/tests/test_external_tool.py index 1bef01a0..a9060178 100644 --- a/tests/test_external_tool.py +++ b/tests/test_external_tool.py @@ -2,80 +2,89 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.account import Account from pycanvas.course import Course from pycanvas.exceptions import CanvasException from pycanvas.external_tool import ExternalTool +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestExternalTool(unittest.TestCase): - """ - Tests Courses functionality - """ - @classmethod - def setUpClass(self): - requires = { - 'account': ['get_by_id'], - 'course': ['get_by_id', 'get_by_id_2'], - 'external_tool': [ - 'get_by_id_account', 'get_by_id_course', 'get_by_id_course_2', - 'get_sessionless_launch_url_course', 'sessionless_launch_no_url' - ], - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - self.course = self.canvas.get_course(1) - self.account = self.canvas.get_account(1) - self.ext_tool_course = self.course.get_external_tool(1) - self.ext_tool_account = self.account.get_external_tool(1) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'account': ['get_by_id'], + 'course': ['get_by_id'], + 'external_tool': ['get_by_id_account', 'get_by_id_course'], + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.account = self.canvas.get_account(1) + self.ext_tool_course = self.course.get_external_tool(1) + self.ext_tool_account = self.account.get_external_tool(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.ext_tool_course) assert isinstance(string, str) # parent_id - def test_parent_id_account(self): + def test_parent_id_account(self, m): assert self.ext_tool_account.parent_id == 1 - def test_parent_id_course(self): + def test_parent_id_course(self, m): assert self.ext_tool_course.parent_id == 1 - def test_parent_id_no_id(self): + def test_parent_id_no_id(self, m): tool = ExternalTool(self.canvas._Canvas__requester, {'id': 1}) with self.assertRaises(ValueError): tool.parent_id # parent_type - def test_parent_type_account(self): + def test_parent_type_account(self, m): assert self.ext_tool_account.parent_type == 'account' - def test_parent_type_course(self): + def test_parent_type_course(self, m): assert self.ext_tool_course.parent_type == 'course' - def test_parent_type_no_id(self): + def test_parent_type_no_id(self, m): tool = ExternalTool(self.canvas._Canvas__requester, {'id': 1}) with self.assertRaises(ValueError): tool.parent_type # get_parent() - def test_get_parent_account(self): + def test_get_parent_account(self, m): + register_uris({'account': ['get_by_id']}, m) assert isinstance(self.ext_tool_account.get_parent(), Account) - def test_get_parent_course(self): + def test_get_parent_course(self, m): + register_uris({'course': ['get_by_id']}, m) assert isinstance(self.ext_tool_course.get_parent(), Course) # get_sessionless_launch_url() - def test_get_sessionless_launch_url(self): + def test_get_sessionless_launch_url(self, m): + requires = {'external_tool': ['get_sessionless_launch_url_course']} + register_uris(requires, m) + assert isinstance(self.ext_tool_course.get_sessionless_launch_url(), (str, unicode)) - def test_get_sessionless_launch_url_no_url(self): + def test_get_sessionless_launch_url_no_url(self, m): + requires = { + 'course': ['get_by_id_2'], + 'external_tool': [ + 'get_by_id_course_2', 'sessionless_launch_no_url' + ] + } + register_uris(requires, m) + course = self.canvas.get_course(2) ext_tool = course.get_external_tool(2) with self.assertRaises(CanvasException): diff --git a/tests/test_group.py b/tests/test_group.py index 0a1fee7a..433895e3 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -1,103 +1,322 @@ +import os import unittest +import uuid import requests_mock -import settings -from util import register_uris from pycanvas import Canvas +from pycanvas.group import Group, GroupMembership, GroupCategory from pycanvas.course import Page from pycanvas.exceptions import RequiredFieldMissing +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestGroup(unittest.TestCase): - """ - Tests Group functionality - """ - @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'show_front_page'], - 'group': [ - 'create_page', 'edit_page', 'show_front_page', - 'get_single_group', 'edit_front_page', - 'get_page', 'get_pages', 'get_pages2' - ] - } - require_generic = { - 'generic': ['not_found'] - } + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, require_generic, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id'], 'group': ['get_by_id']}, m) - self.course = self.canvas.get_course(1) - self.group = self.canvas.get_group(1) - self.page = self.group.get_page('my-url') + self.course = self.canvas.get_course(1) + self.group = self.canvas.get_group(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.group) assert isinstance(string, str) # show_front_page() - def test_show_front_page(self): - front_page = self.group.show_front_page() + def test_show_front_page(self, m): + register_uris({'group': ['show_front_page']}, m) + front_page = self.group.show_front_page() assert isinstance(front_page, Page) assert hasattr(front_page, 'url') assert hasattr(front_page, 'title') # create_front_page() - def test_edit_front_page(self): - new_front_page = self.group.edit_front_page() + def test_edit_front_page(self, m): + register_uris({'group': ['edit_front_page']}, m) + new_front_page = self.group.edit_front_page() assert isinstance(new_front_page, Page) assert hasattr(new_front_page, 'url') assert hasattr(new_front_page, 'title') # list_pages() - def test_get_pages(self): + def test_get_pages(self, m): + register_uris({'group': ['get_pages', 'get_pages2']}, m) + pages = self.group.get_pages() page_list = [page for page in pages] - assert len(page_list) == 4 assert isinstance(page_list[0], Page) - assert hasattr(page_list[0], 'group_id') + assert hasattr(page_list[0], 'id') assert page_list[0].group_id == self.group.id # create_page() - def test_create_page(self): + def test_create_page(self, m): + register_uris({'group': ['create_page']}, m) + title = 'New Page' new_page = self.group.create_page(wiki_page={'title': title}) - assert isinstance(new_page, Page) assert hasattr(new_page, 'title') assert new_page.title == title - assert hasattr(new_page, 'group_id') + assert hasattr(new_page, 'id') assert new_page.group_id == self.group.id - def test_create_page_fail(self): + def test_create_page_fail(self, m): with self.assertRaises(RequiredFieldMissing): self.group.create_page(settings.INVALID_ID) # get_page() - def test_get_page(self): + def test_get_page(self, m): + register_uris({'group': ['get_page']}, m) + url = 'my-url' page = self.group.get_page(url) - assert isinstance(page, Page) # edit() - def test_edit(self): - new_title = "New Page" - self.page.edit(page={'title': new_title}) + def test_edit(self, m): + register_uris({'group': ['edit']}, m) + + new_title = "New Group" + response = self.group.edit(description=new_title) + assert isinstance(response, Group) + assert hasattr(response, 'description') + assert response.description == new_title + + # delete() + def test_delete(self, m): + register_uris({'group': ['delete']}, m) + + group = self.group.delete() + assert isinstance(group, Group) + assert hasattr(group, 'name') + assert hasattr(group, 'description') + + # invite() + def test_invite(self, m): + register_uris({'group': ['invite']}, m) + + user_list = ["1", "2"] + response = self.group.invite(user_list) + gmembership_list = [groupmembership for groupmembership in response] + assert isinstance(gmembership_list[0], GroupMembership) + assert len(gmembership_list) == 2 + + # list_users() + def test_list_users(self, m): + register_uris({'group': ['list_users', 'list_users_p2']}, m) + + from pycanvas.user import User + users = self.group.list_users() + user_list = [user for user in users] + assert isinstance(user_list[0], User) + assert len(user_list) == 4 + + # remove_user() + def test_remove_user(self, m): + register_uris({'group': ['remove_user']}, m) + + from pycanvas.user import User + response = self.group.remove_user(1) + assert isinstance(response, User) + + # upload() + def test_upload(self, m): + register_uris({'group': ['upload', 'upload_final']}, m) + + filename = 'testfile_%s' % uuid.uuid4().hex + file = open(filename, 'w+') + response = self.group.upload(file) + assert response[0] is True + assert isinstance(response[1], dict) + assert 'url' in response[1] + # http://stackoverflow.com/a/10840586 + # Not as stupid as it looks. + try: + os.remove(filename) + except OSError: + pass + + # preview_processed_html() + def test_preview_processed_html(self, m): + register_uris({'group': ['preview_processed_html']}, m) + + html_str = "

processed html

" + response = self.group.preview_html(html_str) + assert response == html_str + + # get_activity_stream_summary() + def test_get_activity_stream_summary(self, m): + register_uris({'group': ['activity_stream_summary']}, m) + + response = self.group.get_activity_stream_summary() + assert len(response) == 2 + assert 'type' in response[0] + + # list_memberships() + def test_list_memberships(self, m): + register_uris({'group': ['list_memberships', 'list_memberships_p2']}, m) + + response = self.group.list_memberships() + membership_list = [membership for membership in response] + assert len(membership_list) == 4 + assert isinstance(membership_list[0], GroupMembership) + assert hasattr(membership_list[0], 'id') + + # get_membership() + def test_get_membership(self, m): + register_uris({'group': ['get_membership']}, m) + + response = self.group.get_membership(1, "users") + assert isinstance(response, GroupMembership) + + # create_membership() + def test_create_membership(self, m): + register_uris({'group': ['create_membership']}, m) + + response = self.group.create_membership(1) + assert isinstance(response, GroupMembership) + + # update_membership() + def test_update_membership(self, m): + register_uris({'group': ['update_membership_user']}, m) + + response = self.group.update_membership(1) + assert isinstance(response, GroupMembership) + + +@requests_mock.Mocker() +class TestGroupMembership(unittest.TestCase): + + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris({'group': ['get_by_id', 'get_membership']}, m) + + self.group = self.canvas.get_group(1) + self.membership = self.group.get_membership(1, "users") + + # __str__() + def test__str__(self, m): + string = str(self.membership) + assert isinstance(string, str) + + # update() + def test_update(self, m): + register_uris({'group': ['update_membership_membership']}, m) + + response = self.membership.update(mem_id=1, moderator=False) + assert isinstance(response, GroupMembership) + + # remove_user() + def test_remove_user(self, m): + register_uris({'group': ['remove_user']}, m) + + response = self.membership.remove_user(1) + # the response should be an empty dict that evaluates to false + assert not response + + # remove_self() + def test_remove_self(self, m): + register_uris({'group': ['remove_self']}, m) + + response = self.membership.remove_self() + # the response should be an empty dict that evaluates to false + assert not response + + +@requests_mock.Mocker() +class TestGroupCategory(unittest.TestCase): + + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id', 'create_group_category']}, m) + + self.course = self.canvas.get_course(1) + self.group_category = self.course.create_group_category("Test String") + + # __str__() + def test__str__(self, m): + string = str(self.group_category) + assert isinstance(string, str) + + # create_group() + def test_create_group(self, m): + register_uris({'group': ['category_create_group']}, m) + + test_str = "Test Create Group" + response = self.group_category.create_group(name=test_str) + assert isinstance(response, Group) + assert hasattr(response, 'name') + assert response.name == test_str + + # update() + def test_update(self, m): + register_uris({'group': ['category_update']}, m) + + new_name = "Test Update Category" + response = self.group_category.update(name=new_name) + assert isinstance(response, GroupCategory) + + # delete_category() + def test_delete_category(self, m): + register_uris({'group': ['category_delete_category']}, m) + + response = self.group_category.delete() + # the response should be an empty dict that evaluates to false + assert not response + + # list_groups() + def test_list_groups(self, m): + register_uris({'group': ['category_list_groups']}, m) + + response = self.group_category.list_groups() + group_list = [group for group in response] + assert len(group_list) == 2 + assert isinstance(group_list[0], Group) + assert hasattr(group_list[0], 'id') + + # list_users() + def test_list_users(self, m): + from pycanvas.user import User + + register_uris({'group': ['category_list_users']}, m) + + response = self.group_category.list_users() + user_list = [user for user in response] + assert len(user_list) == 4 + assert isinstance(user_list[0], User) + assert hasattr(user_list[0], 'user_id') + + # assign_members() + def test_assign_members(self, m): + from pycanvas.progress import Progress + from pycanvas.paginated_list import PaginatedList + + requires = { + 'group': [ + 'category_assign_members_true', + 'category_assign_members_false' + ] + } + register_uris(requires, m) - assert isinstance(self.page, Page) - assert hasattr(self.page, 'title') - assert self.page.title == new_title + result_true = self.group_category.assign_members(sync=True) + return_false = self.group_category.assign_members() - # reset for future tests - self.page = self.group.get_page('my-url') + assert isinstance(result_true, PaginatedList) + assert isinstance(return_false, Progress) diff --git a/tests/test_module.py b/tests/test_module.py index 6d4af0ad..f4e78d69 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -2,35 +2,30 @@ import settings import requests_mock + from pycanvas import Canvas from pycanvas.exceptions import RequiredFieldMissing from pycanvas.module import Module, ModuleItem -from util import register_uris +from tests.util import register_uris +@requests_mock.Mocker() class TestModule(unittest.TestCase): - """ - Tests Module functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'get_module_by_id'], - 'module': [ - 'edit', 'delete', 'relock', 'list_module_items', - 'list_module_items2', 'get_module_item_by_id', 'create_module_item', - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.course = self.canvas.get_course(1) - self.module = self.course.get_module(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id', 'get_module_by_id']}, m) + + self.course = self.canvas.get_course(1) + self.module = self.course.get_module(1) # edit() - def test_edit_module(self): + def test_edit_module(self, m): + register_uris({'module': ['edit']}, m) + name = 'New Name' edited_module = self.module.edit(module={'name': name}) @@ -41,23 +36,29 @@ def test_edit_module(self): assert edited_module.course_id == self.course.id # delete() - def test_delete_module(self): + def test_delete_module(self, m): + register_uris({'module': ['delete']}, m) + deleted_module = self.module.delete() assert isinstance(deleted_module, Module) assert hasattr(deleted_module, 'course_id') assert deleted_module.course_id == self.course.id - #relock() - def test_relock(self): + # relock() + def test_relock(self, m): + register_uris({'module': ['relock']}, m) + relocked_module = self.module.relock() assert isinstance(relocked_module, Module) assert hasattr(relocked_module, 'course_id') assert relocked_module.course_id == self.course.id - #list_module_items() - def test_list_module_items(self): + # list_module_items() + def test_list_module_items(self, m): + register_uris({'module': ['list_module_items', 'list_module_items2']}, m) + module_items = self.module.list_module_items() module_item_list = [module_item for module_item in module_items] @@ -66,16 +67,20 @@ def test_list_module_items(self): assert hasattr(module_item_list[0], 'course_id') assert module_item_list[0].course_id == self.course.id - #get_module_item() - def test_get_module_item(self): + # get_module_item() + def test_get_module_item(self, m): + register_uris({'module': ['get_module_item_by_id']}, m) + module_item = self.module.get_module_item(1) assert isinstance(module_item, ModuleItem) assert hasattr(module_item, 'course_id') assert module_item.course_id == self.course.id - #create_module_item() - def test_create_module_item(self): + # create_module_item() + def test_create_module_item(self, m): + register_uris({'module': ['create_module_item']}, m) + module_item = self.module.create_module_item( module_item={ 'type': 'Page', @@ -86,49 +91,46 @@ def test_create_module_item(self): assert hasattr(module_item, 'course_id') assert module_item.course_id == self.course.id - def test_create_module_item_fail1(self): + def test_create_module_item_fail1(self, m): with self.assertRaises(RequiredFieldMissing): self.module.create_module_item( module_item={'content_id': 1} ) - def test_create_module_item_fail2(self): + def test_create_module_item_fail2(self, m): with self.assertRaises(RequiredFieldMissing): self.module.create_module_item( module_item={'type': 'Page'} ) - #__str__ - def test__str__(self): + # __str__ + def test__str__(self, m): string = str(self.module) assert isinstance(string, str) +@requests_mock.Mocker() class TestModuleItem(unittest.TestCase): - """ - Tests Module Item functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'get_module_by_id'], - 'module': [ - 'get_module_item_by_id', 'edit_module_item', - 'delete_module_item', 'complete_module_item', - 'uncomplete_module_item' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.course = self.canvas.get_course(1) - self.module = self.course.get_module(1) - self.module_item = self.module.get_module_item(1) - - #edit() - def test_edit_module_item(self): + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'get_module_by_id'], + 'module': ['get_module_item_by_id'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.module = self.course.get_module(1) + self.module_item = self.module.get_module_item(1) + + # edit() + def test_edit_module_item(self, m): + register_uris({'module': ['edit_module_item']}, m) + title = 'New Title' edited_module_item = self.module_item.edit( module_item={'title': title} @@ -140,16 +142,20 @@ def test_edit_module_item(self): assert hasattr(edited_module_item, 'course_id') assert edited_module_item.course_id == self.course.id - #delete() - def test_delete(self): + # delete() + def test_delete(self, m): + register_uris({'module': ['delete_module_item']}, m) + deleted_module_item = self.module_item.delete() assert isinstance(deleted_module_item, ModuleItem) assert hasattr(deleted_module_item, 'course_id') assert deleted_module_item.course_id == self.course.id - #complete(course_id, True) - def test_complete(self): + # complete(course_id, True) + def test_complete(self, m): + register_uris({'module': ['complete_module_item']}, m) + completed_module_item = self.module_item.complete() assert isinstance(completed_module_item, ModuleItem) @@ -157,8 +163,10 @@ def test_complete(self): assert hasattr(completed_module_item, 'course_id') assert completed_module_item.course_id == self.course.id - #complete(course_id, False) - def test_uncomplete(self): + # complete(course_id, False) + def test_uncomplete(self, m): + register_uris({'module': ['uncomplete_module_item']}, m) + completed_module_item = self.module_item.uncomplete() assert isinstance(completed_module_item, ModuleItem) @@ -166,6 +174,6 @@ def test_uncomplete(self): assert hasattr(completed_module_item, 'course_id') assert completed_module_item.course_id == self.course.id - def test__str__(self): + def test__str__(self, m): string = str(self.module_item) assert isinstance(string, str) diff --git a/tests/test_page.py b/tests/test_page.py index cdebd74c..687c7649 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -2,40 +2,42 @@ import requests_mock -import settings -from util import register_uris from pycanvas.canvas import Canvas from pycanvas.course import Course from pycanvas.group import Group -from pycanvas.page import Page +from pycanvas.page import Page, PageRevision +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestPage(unittest.TestCase): - """ - Test Page methods - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id', 'get_page', 'edit', 'delete_page'], - 'group': ['get_single_group', 'get_page'], - 'generic': ['not_found'], - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.course = self.canvas.get_course(1) - self.group = self.canvas.get_group(1) - self.page_course = self.course.get_page('my-url') - self.page_group = self.group.get_page('my-url') - - #__str__() - def test__str__(self): + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id'], + 'group': ['get_by_id', 'pages_get_page'], + 'page': ['get_page'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.group = self.canvas.get_group(1) + self.page_course = self.course.get_page('my-url') + self.page_group = self.group.get_page('my-url') + + # __str__() + def test__str__(self, m): string = str(self.page_course) assert isinstance(string, str) - def test_edit(self): + def test_edit(self, m): + register_uris({'page': ['edit']}, m) + new_title = "New Page" self.page_course.edit(page={'title': new_title}) @@ -43,42 +45,149 @@ def test_edit(self): assert hasattr(self.page_course, 'title') assert self.page_course.title == new_title - #reset for future tests - self.page_course = self.course.get_page('my-url') + def test_delete(self, m): + register_uris({'page': ['delete_page']}, m) - def test_delete(self): - page = self.course.get_page('my-url') + page = self.page_course deleted_page = page.delete() assert isinstance(deleted_page, Page) + def test_list_revisions(self, m): + register_uris({'page': ['list_revisions', 'list_revisions2']}, m) + + revisions = self.page_course.list_revisions() + rev_list = [rev for rev in revisions] + + assert len(rev_list) == 4 + assert isinstance(rev_list[0], PageRevision) + + def test_show_latest_revision(self, m): + register_uris({'page': ['latest_revision']}, m) + + revision = self.page_course.show_latest_revision() + + assert isinstance(revision, PageRevision) + + def test_get_revision_by_id_course(self, m): + register_uris({'page': ['get_latest_rev_by_id']}, m) + + revision = self.page_course.get_revision_by_id(2) + + assert isinstance(revision, PageRevision) + + def test_get_revision_by_id_group(self, m): + register_uris({'page': ['get_latest_rev_by_id_group']}, m) + + revision = self.page_group.get_revision_by_id(2) + + assert isinstance(revision, PageRevision) + + def test_revert_to_revision_course(self, m): + register_uris({'page': ['revert_to_revision']}, m) + + revision = self.page_course.revert_to_revision(3) + + assert isinstance(revision, PageRevision) + + def test_revert_to_revision_group(self, m): + register_uris({'page': ['revert_to_revision_group']}, m) + + revision = self.page_group.revert_to_revision(3) + + assert isinstance(revision, PageRevision) + # parent_id - def test_parent_id_course(self): + def test_parent_id_course(self, m): assert self.page_course.parent_id == 1 - def test_parent_id_group(self): + def test_parent_id_group(self, m): assert self.page_group.parent_id == 1 - def test_parent_id_no_id(self): + def test_parent_id_no_id(self, m): page = Page(self.canvas._Canvas__requester, {'url': 'my-url'}) with self.assertRaises(ValueError): page.parent_id # parent_type - def test_parent_type_course(self): + def test_parent_type_course(self, m): assert self.page_course.parent_type == 'course' - def test_parent_type_group(self): + def test_parent_type_group(self, m): assert self.page_group.parent_type == 'group' - def test_parent_type_no_id(self): + def test_parent_type_no_id(self, m): page = Page(self.canvas._Canvas__requester, {'url': 'my-url'}) with self.assertRaises(ValueError): page.parent_type # get_parent() - def test_get_parent_course(self): + def test_get_parent_course(self, m): + register_uris({'course': ['get_by_id']}, m) + assert isinstance(self.page_course.get_parent(), Course) - def test_get_parent_group(self): + def test_get_parent_group(self, m): + register_uris({'group': ['get_by_id']}, m) + assert isinstance(self.page_group.get_parent(), Group) + + +@requests_mock.Mocker() +class TestPageRevision(unittest.TestCase): + + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'get_page'], + 'group': ['get_by_id', 'pages_get_page'], + 'page': ['get_latest_rev_by_id', 'get_latest_rev_by_id_group'] + } + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.group = self.canvas.get_group(1) + self.page_course = self.course.get_page('my-url') + self.page_group = self.group.get_page('my-url') + self.revision = self.page_course.get_revision_by_id(2) + self.group_revision = self.page_group.get_revision_by_id(2) + + # __str__() + def test__str__(self, m): + string = str(self.revision) + assert isinstance(string, str) + + # parent_id + def test_parent_id_course(self, m): + assert self.revision.parent_id == 1 + + def test_parent_id_no_id(self, m): + page = PageRevision(self.canvas._Canvas__requester, {'url': 'my-url'}) + with self.assertRaises(ValueError): + page.parent_id + + # parent_type + def test_parent_type_course(self, m): + assert self.page_course.parent_type == 'course' + + def test_parent_type_group(self, m): + assert self.page_group.parent_type == 'group' + + def test_parent_type_no_id(self, m): + page = PageRevision(self.canvas._Canvas__requester, {'url': 'my-url'}) + with self.assertRaises(ValueError): + page.parent_type + + # get_parent() + def test_get_parent_course(self, m): + register_uris({'course': ['get_by_id']}, m) + + assert isinstance(self.revision.get_parent(), Course) + + def test_get_parent_group(self, m): + register_uris({'group': ['get_by_id']}, m) + + assert isinstance(self.group_revision.get_parent(), Group) diff --git a/tests/test_page_view.py b/tests/test_page_view.py index 3ea2688d..236eb3c4 100644 --- a/tests/test_page_view.py +++ b/tests/test_page_view.py @@ -2,31 +2,26 @@ import requests_mock -import settings from pycanvas import Canvas +from tests import settings from util import register_uris +@requests_mock.Mocker() class TestPageView(unittest.TestCase): - """ - Tests PageView functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'user': ['get_by_id', 'page_views', 'page_views_p2'] - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'user': ['get_by_id', 'page_views', 'page_views_p2']}, m) - self.user = self.canvas.get_user(1) - pageviews = self.user.get_page_views() - self.pageview = pageviews[0] + self.user = self.canvas.get_user(1) + pageviews = self.user.get_page_views() + self.pageview = pageviews[0] # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.pageview) assert isinstance(string, str) diff --git a/tests/test_paginated_list.py b/tests/test_paginated_list.py index c1895cb1..0ef90f2d 100644 --- a/tests/test_paginated_list.py +++ b/tests/test_paginated_list.py @@ -2,33 +2,25 @@ import requests_mock -import settings from pycanvas import Canvas from pycanvas.paginated_list import PaginatedList from pycanvas.user import User -from util import register_uris +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestPaginatedList(unittest.TestCase): - """ - Tests PaginatedList functionality. - """ - @classmethod - def setUpClass(self): - requires = { - 'paginated_list': [ - '2_1_page', '4_2_pages_p1', '4_2_pages_p2', '6_3_pages_p1', - '6_3_pages_p2', '6_3_pages_p3', 'empty', 'single', - ] - } - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) self.requester = self.canvas._Canvas__requester - register_uris(settings.BASE_URL, requires, adapter) # various length lists - def test_paginated_list_empty(self): + def test_paginated_list_empty(self, m): + register_uris({'paginated_list': ['empty']}, m) + pag_list = PaginatedList( User, self.requester, @@ -38,7 +30,9 @@ def test_paginated_list_empty(self): item_list = [item for item in pag_list] assert len(item_list) == 0 - def test_paginated_list_single(self): + def test_paginated_list_single(self, m): + register_uris({'paginated_list': ['single']}, m) + pag_list = PaginatedList( User, self.requester, @@ -49,7 +43,9 @@ def test_paginated_list_single(self): assert len(item_list) == 1 assert isinstance(item_list[0], User) - def test_paginated_list_two_one_page(self): + def test_paginated_list_two_one_page(self, m): + register_uris({'paginated_list': ['2_1_page']}, m) + pag_list = PaginatedList( User, self.requester, @@ -60,7 +56,9 @@ def test_paginated_list_two_one_page(self): assert len(item_list) == 2 assert isinstance(item_list[0], User) - def test_paginated_list_four_two_pages(self): + def test_paginated_list_four_two_pages(self, m): + register_uris({'paginated_list': ['4_2_pages_p1', '4_2_pages_p2']}, m) + pag_list = PaginatedList( User, self.requester, @@ -71,7 +69,12 @@ def test_paginated_list_four_two_pages(self): assert len(item_list) == 4 assert isinstance(item_list[0], User) - def test_paginated_list_six_three_pages(self): + def test_paginated_list_six_three_pages(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -83,7 +86,12 @@ def test_paginated_list_six_three_pages(self): assert isinstance(item_list[0], User) # reusing iterator - def test_iterator(self): + def test_iterator(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -95,7 +103,12 @@ def test_iterator(self): assert cmp(list_1, list_2) == 0 # get item - def test_getitem_first(self): + def test_getitem_first(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -105,7 +118,12 @@ def test_getitem_first(self): first_item = pag_list[0] assert isinstance(first_item, User) - def test_getitem_second_page(self): + def test_getitem_second_page(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -116,7 +134,12 @@ def test_getitem_second_page(self): assert isinstance(third_item, User) # slicing - def test_slice_beginning(self): + def test_slice_beginning(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -130,7 +153,12 @@ def test_slice_beginning(self): assert hasattr(item_list[0], 'id') assert item_list[0].id == '1' - def test_slice_middle(self): + def test_slice_middle(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -144,7 +172,12 @@ def test_slice_middle(self): assert hasattr(item_list[0], 'id') assert item_list[0].id == '3' - def test_slice_end(self): + def test_slice_end(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, @@ -159,7 +192,12 @@ def test_slice_end(self): assert item_list[0].id == '5' # __repr__() - def test_repr(self): + def test_repr(self, m): + requires = { + 'paginated_list': ['6_3_pages_p1', '6_3_pages_p2', '6_3_pages_p3'] + } + register_uris(requires, m) + pag_list = PaginatedList( User, self.requester, diff --git a/tests/test_progress.py b/tests/test_progress.py new file mode 100644 index 00000000..cf3b8b2b --- /dev/null +++ b/tests/test_progress.py @@ -0,0 +1,40 @@ +import unittest + +import requests_mock + +import settings +from pycanvas.canvas import Canvas +from pycanvas.progress import Progress +from util import register_uris + + +@requests_mock.Mocker() +class TestProgress(unittest.TestCase): + + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + requires = { + 'course': ['get_by_id', 'create_group_category'], + 'group': ['category_assign_members_false'] + } + + register_uris(requires, m) + + self.course = self.canvas.get_course(1) + self.group_category = self.course.create_group_category("Test String") + self.progress = self.group_category.assign_members() + + # __str__() + def test__str__(self, m): + string = str(self.progress) + assert isinstance(string, str) + + # query() + def test_query(self, m): + register_uris({'progress': ['progress_query']}, m) + + response = self.progress.query() + assert isinstance(response, Progress) diff --git a/tests/test_quiz.py b/tests/test_quiz.py index 6fd447c0..7945077d 100644 --- a/tests/test_quiz.py +++ b/tests/test_quiz.py @@ -2,37 +2,34 @@ import settings import requests_mock + from pycanvas import Canvas from pycanvas.quiz import Quiz -from util import register_uris +from tests.util import register_uris +@requests_mock.Mocker() class TestQuiz(unittest.TestCase): - """ - Tests Quiz functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'course': ['get_by_id'], - 'generic': ['not_found'], - 'quiz': ['delete', 'edit', 'get_by_id'], - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'course': ['get_by_id'], 'quiz': ['get_by_id']}, m) - self.course = self.canvas.get_course(1) - self.quiz = self.course.get_quiz(1) + self.course = self.canvas.get_course(1) + self.quiz = self.course.get_quiz(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.quiz) assert isinstance(string, str) # edit() - def test_edit(self): + def test_edit(self, m): + register_uris({'quiz': ['edit']}, m) + title = 'New Title' edited_quiz = self.quiz.edit(quiz={'title': title}) @@ -43,7 +40,9 @@ def test_edit(self): assert edited_quiz.course_id == self.course.id # delete() - def test_delete(self): + def test_delete(self, m): + register_uris({'quiz': ['delete']}, m) + title = "Great Title" deleted_quiz = self.quiz.delete(quiz={'title': title}) diff --git a/tests/test_requester.py b/tests/test_requester.py index 2a40a995..57bcfb04 100644 --- a/tests/test_requester.py +++ b/tests/test_requester.py @@ -2,59 +2,74 @@ import requests_mock -import settings from pycanvas import Canvas -from pycanvas.exceptions import BadRequest, CanvasException, PermissionError, ResourceDoesNotExist -from util import register_uris +from pycanvas.exceptions import ( + BadRequest, CanvasException, InvalidAccessToken, ResourceDoesNotExist, + Unauthorized +) +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestRequester(unittest.TestCase): - """ - Tests Requester functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'requests': [ - '400', '401', '404', '500', - 'delete', 'get', 'post', 'put' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) self.requester = self.canvas._Canvas__requester - register_uris(settings.BASE_URL, requires, adapter) # request() - def test_request_get(self): + def test_request_get(self, m): + register_uris({'requests': ['get']}, m) + response = self.requester.request('GET', 'fake_get_request') assert response.status_code == 200 - def test_request_post(self): + def test_request_post(self, m): + register_uris({'requests': ['post']}, m) + response = self.requester.request('POST', 'fake_post_request') assert response.status_code == 200 - def test_request_delete(self): + def test_request_delete(self, m): + register_uris({'requests': ['delete']}, m) + response = self.requester.request('DELETE', 'fake_delete_request') assert response.status_code == 200 - def test_request_put(self): + def test_request_put(self, m): + register_uris({'requests': ['put']}, m) + response = self.requester.request('PUT', 'fake_put_request') assert response.status_code == 200 - def test_request_400(self): + def test_request_400(self, m): + register_uris({'requests': ['400']}, m) + with self.assertRaises(BadRequest): self.requester.request('GET', '400') - def test_request_401(self): - with self.assertRaises(PermissionError): - self.requester.request('GET', '401') + def test_request_401_InvalidAccessToken(self, m): + register_uris({'requests': ['401_invalid_access_token']}, m) + + with self.assertRaises(InvalidAccessToken): + self.requester.request('GET', '401_invalid_access_token') + + def test_request_401_Unauthorized(self, m): + register_uris({'requests': ['401_unauthorized']}, m) + + with self.assertRaises(Unauthorized): + self.requester.request('GET', '401_unauthorized') + + def test_request_404(self, m): + register_uris({'requests': ['404']}, m) - def test_request_404(self): with self.assertRaises(ResourceDoesNotExist): self.requester.request('GET', '404') - def test_request_500(self): + def test_request_500(self, m): + register_uris({'requests': ['500']}, m) + with self.assertRaises(CanvasException): self.requester.request('GET', '500') diff --git a/tests/test_section.py b/tests/test_section.py index 7d2ec8fd..109adbd5 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -2,37 +2,63 @@ import settings import requests_mock -from util import register_uris -from pycanvas.enrollment import Enrollment from pycanvas import Canvas +from pycanvas.enrollment import Enrollment +from pycanvas.section import Section +from tests.util import register_uris +@requests_mock.Mocker() class TestSection(unittest.TestCase): - """ - Tests core Section functionality - """ + @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'section': ['get_by_id', 'list_enrollments', 'list_enrollments_2'] - } + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + with requests_mock.Mocker() as m: + register_uris({'section': ['get_by_id']}, m) - self.section = self.canvas.get_section(1) + self.section = self.canvas.get_section(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.section) assert isinstance(string, str) # list_enrollments() - def test_get_enrollments(self): + def test_get_enrollments(self, m): + register_uris({'section': ['list_enrollments', 'list_enrollments_2']}, m) + enrollments = self.section.get_enrollments() enrollment_list = [enrollment for enrollment in enrollments] assert len(enrollment_list) == 4 assert isinstance(enrollment_list[0], Enrollment) + + def test_cross_list_section(self, m): + register_uris({'section': ['crosslist_section']}, m) + + section = self.section.cross_list_section(2) + + assert isinstance(section, Section) + + def test_decross_list_section(self, m): + register_uris({'section': ['decross_section']}, m) + + section = self.section.decross_list_section() + + assert isinstance(section, Section) + + def test_edit(self, m): + register_uris({'section': ['edit']}, m) + + edit = self.section.edit() + + assert isinstance(edit, Section) + + def test_delete(self, m): + register_uris({'section': ['delete']}, m) + + deleted_section = self.section.delete() + + assert isinstance(deleted_section, Section) diff --git a/tests/test_uploader.py b/tests/test_uploader.py index f7d38037..8991f428 100644 --- a/tests/test_uploader.py +++ b/tests/test_uploader.py @@ -6,29 +6,18 @@ from pycanvas.canvas import Canvas from pycanvas.upload import Uploader -import settings -from util import register_uris +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestUploader(unittest.TestCase): @classmethod - def setUpClass(self): - requires = { - 'uploader': [ - 'upload_response', 'upload_response_upload_url', - 'upload_response_no_upload_url', 'upload_response_no_upload_params', - 'upload_response_fail', 'upload_fail' - ] - } - - adapter = requests_mock.Adapter() - register_uris(settings.BASE_URL, requires, adapter) - - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) self.requester = self.canvas._Canvas__requester - def setUp(self): self.filename = 'testfile_%s' % uuid.uuid4().hex self.file = open(self.filename, 'w+') @@ -41,7 +30,12 @@ def tearDown(self): pass # start() - def test_start(self): + def test_start(self, m): + requires = { + 'uploader': ['upload_response', 'upload_response_upload_url'] + } + register_uris(requires, m) + uploader = Uploader(self.requester, 'upload_response', self.file) result = uploader.start() @@ -49,8 +43,12 @@ def test_start(self): assert isinstance(result[1], dict) assert 'url' in result[1] - # start() - def test_start_path(self): + def test_start_path(self, m): + requires = { + 'uploader': ['upload_response', 'upload_response_upload_url'] + } + register_uris(requires, m) + uploader = Uploader(self.requester, 'upload_response', self.filename) result = uploader.start() @@ -58,23 +56,29 @@ def test_start_path(self): assert isinstance(result[1], dict) assert 'url' in result[1] - # start() - def test_start_file_does_not_exist(self): + def test_start_file_does_not_exist(self, m): with self.assertRaises(IOError): Uploader(self.requester, 'upload_response', 'test_file_not_real.xyz') # upload() - def test_upload_no_upload_url(self): - with self.assertRaises(Exception): + def test_upload_no_upload_url(self, m): + register_uris({'uploader': ['upload_response_no_upload_url']}, m) + + with self.assertRaises(ValueError): Uploader(self.requester, 'upload_response_no_upload_url', self.filename).start() - # upload() - def test_upload_no_upload_params(self): - with self.assertRaises(Exception): + def test_upload_no_upload_params(self, m): + register_uris({'uploader': ['upload_response_no_upload_params']}, m) + + with self.assertRaises(ValueError): Uploader(self.requester, 'upload_response_no_upload_params', self.filename).start() - # upload() - def test_upload_fail(self): + def test_upload_fail(self, m): + requires = { + 'uploader': ['upload_fail', 'upload_response_fail'] + } + register_uris(requires, m) + uploader = Uploader(self.requester, 'upload_response_fail', self.file) result = uploader.start() diff --git a/tests/test_user.py b/tests/test_user.py index 178d64a5..14a6f21a 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -4,55 +4,48 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.assignment import Assignment from pycanvas.avatar import Avatar from pycanvas.course import Course +from pycanvas.group import Group from pycanvas.enrollment import Enrollment from pycanvas.page_view import PageView from pycanvas.user import User +from tests import settings +from tests.util import register_uris +@requests_mock.Mocker() class TestUser(unittest.TestCase): - """ - Tests User functionality. - """ + @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'user': [ - 'avatars', 'avatars_p2', 'color', 'color_update', 'colors', - 'courses', 'courses_p2', 'edit', 'get_by_id', 'get_by_id_2', - 'get_user_assignments', 'get_user_assignments2', - 'list_enrollments', 'list_enrollments_2', 'merge', - 'missing_sub', 'missing_sub_p2', 'page_views', 'page_views_p2', - 'profile', 'update_settings', 'upload', 'upload_final' - ] - } - - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) - - self.user = self.canvas.get_user(1) + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris({'user': ['get_by_id']}, m) + + self.user = self.canvas.get_user(1) # __str__() - def test__str__(self): + def test__str__(self, m): string = str(self.user) assert isinstance(string, str) # get_profile() - def test_get_profile(self): + def test_get_profile(self, m): + register_uris({'user': ['profile']}, m) + profile = self.user.get_profile() assert isinstance(profile, dict) assert 'name' in profile # get_page_views() - def test_get_page_views(self): + def test_get_page_views(self, m): + register_uris({'user': ['page_views', 'page_views_p2']}, m) + page_views = self.user.get_page_views() page_view_list = [view for view in page_views] @@ -60,7 +53,9 @@ def test_get_page_views(self): assert isinstance(page_view_list[0], PageView) # get_courses() - def test_get_courses(self): + def test_get_courses(self, m): + register_uris({'user': ['courses', 'courses_p2']}, m) + courses = self.user.get_courses() course_list = [course for course in courses] @@ -68,7 +63,9 @@ def test_get_courses(self): assert isinstance(course_list[0], Course) # get_missing_submissions() - def test_get_missing_submissions(self): + def test_get_missing_submissions(self, m): + register_uris({'user': ['missing_sub', 'missing_sub_p2']}, m) + missing_assigments = self.user.get_missing_submissions() assignment_list = [assignment for assignment in missing_assigments] @@ -76,7 +73,9 @@ def test_get_missing_submissions(self): assert isinstance(assignment_list[0], Assignment) # update_settings() - def test_update_settings(self): + def test_update_settings(self, m): + register_uris({'user': ['update_settings']}, m) + settings = self.user.update_settings(manual_mark_as_read=True) assert isinstance(settings, dict) @@ -84,7 +83,9 @@ def test_update_settings(self): assert settings['manual_mark_as_read'] is True # get_color() - def test_get_color(self): + def test_get_color(self, m): + register_uris({'user': ['color']}, m) + color = self.user.get_color("course_1") assert isinstance(color, dict) @@ -92,7 +93,9 @@ def test_get_color(self): assert color['hexcode'] == "#abc123" # get_colors() - def test_get_colors(self): + def test_get_colors(self, m): + register_uris({'user': ['colors']}, m) + colors = self.user.get_colors() assert isinstance(colors, dict) @@ -100,7 +103,9 @@ def test_get_colors(self): assert isinstance(colors['custom_colors'], dict) # update_color() - def test_update_color(self): + def test_update_color(self, m): + register_uris({'user': ['color_update']}, m) + new_hexcode = "#f00f00" color = self.user.update_color("course_1", new_hexcode) @@ -108,7 +113,9 @@ def test_update_color(self): assert 'hexcode' in color assert color['hexcode'] == new_hexcode - def test_update_color_no_hashtag(self): + def test_update_color_no_hashtag(self, m): + register_uris({'user': ['color_update']}, m) + new_hexcode = "f00f00" color = self.user.update_color("course_1", new_hexcode) @@ -117,7 +124,9 @@ def test_update_color_no_hashtag(self): assert color['hexcode'] == "#" + new_hexcode # edit() - def test_edit(self): + def test_edit(self, m): + register_uris({'user': ['edit']}, m) + new_name = "New User Name" self.user.edit(user={'name': new_name}) @@ -125,21 +134,19 @@ def test_edit(self): assert hasattr(self.user, 'name') assert self.user.name == new_name - # reset for future tests - self.user = self.canvas.get_user(1) - # merge_into() - def test_merge_into_id(self): + def test_merge_into_id(self, m): + register_uris({'user': ['merge']}, m) + self.user.merge_into(2) assert isinstance(self.user, User) assert hasattr(self.user, 'name') assert self.user.name == 'John Smith' - # reset for future tests - self.user = self.canvas.get_user(1) + def test_merge_into_user(self, m): + register_uris({'user': ['get_by_id_2', 'merge']}, m) - def test_merge_into_user(self): other_user = self.canvas.get_user(2) self.user.merge_into(other_user) @@ -147,11 +154,10 @@ def test_merge_into_user(self): assert hasattr(self.user, 'name') assert self.user.name == 'John Smith' - # reset for future tests - self.user = self.canvas.get_user(1) - # get_avatars() - def test_get_avatars(self): + def test_get_avatars(self, m): + register_uris({'user': ['avatars', 'avatars_p2']}, m) + avatars = self.user.get_avatars() avatar_list = [avatar for avatar in avatars] @@ -159,17 +165,19 @@ def test_get_avatars(self): assert isinstance(avatar_list[0], Avatar) # get_assignments() - def test_user_assignments(self): - user = self.canvas.get_user(1) + def test_user_assignments(self, m): + register_uris({'user': ['get_user_assignments', 'get_user_assignments2']}, m) - assignments = user.get_assignments(1) + assignments = self.user.get_assignments(1) assignment_list = [assignment for assignment in assignments] assert isinstance(assignments[0], Assignment) assert len(assignment_list) == 4 - #list_enrollments() - def test_list_enrollments(self): + # list_enrollments() + def test_list_enrollments(self, m): + register_uris({'user': ['list_enrollments', 'list_enrollments_2']}, m) + enrollments = self.user.get_enrollments() enrollment_list = [enrollment for enrollment in enrollments] @@ -177,7 +185,9 @@ def test_list_enrollments(self): assert isinstance(enrollment_list[0], Enrollment) # upload() - def test_upload(self): + def test_upload(self, m): + register_uris({'user': ['upload', 'upload_final']}, m) + filename = 'testfile_%s' % uuid.uuid4().hex file = open(filename, 'w+') @@ -193,3 +203,12 @@ def test_upload(self): os.remove(filename) except OSError: pass + + def test_list_groups(self, m): + register_uris({'user': ['list_groups', 'list_groups2']}, m) + + groups = self.user.list_groups() + group_list = [group for group in groups] + + assert len(group_list) == 4 + assert isinstance(group_list[0], Group) diff --git a/tests/test_util.py b/tests/test_util.py index 9733d2bb..a6013b8f 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -2,72 +2,227 @@ import requests_mock -import settings -from util import register_uris from pycanvas import Canvas from pycanvas.course import CourseNickname from pycanvas.user import User from pycanvas.util import combine_kwargs, obj_or_id +from tests import settings +from tests.util import register_uris -class TestCourse(unittest.TestCase): - """ - Tests utility methods - """ - @classmethod - def setUpClass(self): - requires = { - 'generic': ['not_found'], - 'user': ['get_by_id', 'course_nickname'] - } +@requests_mock.Mocker() +class TestUtil(unittest.TestCase): - adapter = requests_mock.Adapter() - self.canvas = Canvas(settings.BASE_URL, settings.API_KEY, adapter) - register_uris(settings.BASE_URL, requires, adapter) + @classmethod + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) # combine_kwargs() - def test_combine_kwargs(self): - key1_dict = { - 'subkey1-1': 'value1', - 'subkey1-2': 'value2', - 'subkey1-3': 'value3', - } - key2_dict = { - 'subkey2-1': 'value4', - 'subkey2-2': 'value5', - 'subkey2-3': 'value6', - } - result = combine_kwargs(key1=key1_dict, key2=key2_dict) - - assert isinstance(result, dict) + def test_combine_kwargs_empty(self, m): + + result = combine_kwargs() + self.assertIsInstance(result, dict) + self.assertEqual(result, {}) + + def test_combine_kwargs_single(self, m): + result = combine_kwargs(var='test') + self.assertIsInstance(result, dict) + self.assertTrue('var' in result) + self.assertEqual(result['var'], 'test') + + def test_combine_kwargs_single_dict(self, m): + result = combine_kwargs(var={'foo': 'bar'}) + self.assertIsInstance(result, dict) + self.assertTrue('var[foo]' in result) + self.assertEqual(result['var[foo]'], 'bar') + + def test_combine_kwargs_multiple_dicts(self, m): + result = combine_kwargs( + var1={'foo': 'bar'}, + var2={'fizz': 'buzz'} + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 2) + + self.assertIn('var1[foo]', result) + self.assertEqual(result['var1[foo]'], 'bar') + + self.assertIn('var2[fizz]', result) + self.assertEqual(result['var2[fizz]'], 'buzz') + + def test_combine_kwargs_multiple_mixed(self, m): + result = combine_kwargs( + var1=True, + var2={'fizz': 'buzz'}, + var3='foo', + var4=42, + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 4) + + expected_keys = ['var1', 'var2[fizz]', 'var3', 'var4'] + self.assertTrue(all(key in result for key in expected_keys)) + + self.assertEqual(result['var1'], True) + self.assertEqual(result['var2[fizz]'], 'buzz') + self.assertEqual(result['var3'], 'foo') + self.assertEqual(result['var4'], 42) + + def test_combine_kwargs_nested_dict(self, m): + result = combine_kwargs(dict={ + 'key': {'subkey': 'value'} + }) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 1) + + self.assertIn('dict[key][subkey]', result) + self.assertEqual(result['dict[key][subkey]'], 'value') + + def test_combine_kwargs_multiple_nested_dicts(self, m): + result = combine_kwargs( + dict1={ + 'key1': { + 'subkey1-1': 'value1-1', + 'subkey1-2': 'value1-2' + }, + 'key2': { + 'subkey2-1': 'value2-1', + 'subkey2-2': 'value2-2' + } + }, + dict2={ + 'key1': { + 'subkey1-1': 'value1-1', + 'subkey1-2': 'value1-2' + }, + 'key2': { + 'subkey2-1': 'value2-1', + 'subkey2-2': 'value2-2' + } + } + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 8) + expected_keys = [ - 'key1[subkey1-1]', - 'key1[subkey1-2]', - 'key1[subkey1-3]', - 'key2[subkey2-1]', - 'key2[subkey2-2]', - 'key2[subkey2-3]' + 'dict1[key1][subkey1-1]', + 'dict1[key1][subkey1-2]', + 'dict1[key2][subkey2-1]', + 'dict1[key2][subkey2-2]', + 'dict2[key1][subkey1-1]', + 'dict2[key1][subkey1-2]', + 'dict2[key2][subkey2-1]', + 'dict2[key2][subkey2-2]', ] - assert all(key in result for key in expected_keys) + self.assertTrue(all(key in result for key in expected_keys)) + + self.assertEqual(result['dict1[key1][subkey1-1]'], 'value1-1') + self.assertEqual(result['dict1[key1][subkey1-2]'], 'value1-2') + self.assertEqual(result['dict1[key2][subkey2-1]'], 'value2-1') + self.assertEqual(result['dict1[key2][subkey2-2]'], 'value2-2') + self.assertEqual(result['dict2[key1][subkey1-1]'], 'value1-1') + self.assertEqual(result['dict2[key1][subkey1-2]'], 'value1-2') + self.assertEqual(result['dict2[key2][subkey2-1]'], 'value2-1') + self.assertEqual(result['dict2[key2][subkey2-2]'], 'value2-2') + + def test_combine_kwargs_super_nested_dict(self, m): + result = combine_kwargs( + big_dict={'a': {'b': {'c': {'d': {'e': 'We need to go deeper'}}}}} + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 1) + self.assertIn('big_dict[a][b][c][d][e]', result) + self.assertEqual(result['big_dict[a][b][c][d][e]'], 'We need to go deeper') + + def test_combine_kwargs_the_gauntlet(self, m): + result = combine_kwargs( + foo='bar', + fb={ + 3: 'fizz', + 5: 'buzz', + 15: 'fizzbuzz' + }, + true=False, + life=42, + days_of_xmas={ + 'first': { + 1: 'partridge in a pear tree' + }, + 'second': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + }, + 'third': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + 3: 'french hens' + }, + 'fourth': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + 3: 'french hens', + '4': 'mocking birds', + }, + 'fifth': { + 1: 'partridge in a pear tree', + '2': 'turtle doves', + 3: 'french hens', + '4': 'mocking birds', + '5': 'GOLDEN RINGS' + } + }, + super_nest={'1': {'2': {'3': {'4': {'5': {'6': 'tada'}}}}}} + ) + self.assertIsInstance(result, dict) + self.assertEqual(len(result), 22) + + expected_keys = [ + 'foo', + 'fb[3]', + 'fb[5]', + 'fb[15]', + 'true', + 'life', + 'days_of_xmas[first][1]', + 'days_of_xmas[second][1]', + 'days_of_xmas[second][2]', + 'days_of_xmas[third][1]', + 'days_of_xmas[third][2]', + 'days_of_xmas[third][3]', + 'days_of_xmas[fourth][1]', + 'days_of_xmas[fourth][2]', + 'days_of_xmas[fourth][3]', + 'days_of_xmas[fourth][4]', + 'days_of_xmas[fifth][1]', + 'days_of_xmas[fifth][2]', + 'days_of_xmas[fifth][3]', + 'days_of_xmas[fifth][4]', + 'days_of_xmas[fifth][5]', + 'super_nest[1][2][3][4][5][6]' + ] + + self.assertTrue(all(key in result for key in expected_keys)) # obj_or_id() - def test_obj_or_id_int(self): + def test_obj_or_id_int(self, m): user_id = obj_or_id(1, 'user_id', (User,)) assert isinstance(user_id, int) assert user_id == 1 - def test_obj_or_id_str_valid(self): + def test_obj_or_id_str_valid(self, m): user_id = obj_or_id("1", 'user_id', (User,)) assert isinstance(user_id, int) assert user_id == 1 - def test_obj_or_id_str_invalid(self): + def test_obj_or_id_str_invalid(self, m): with self.assertRaises(TypeError): obj_or_id("1a", 'user_id', (User,)) - def test_obj_or_id_obj(self): + def test_obj_or_id_obj(self, m): + register_uris({'user': ['get_by_id']}, m) + user = self.canvas.get_user(1) user_id = obj_or_id(user, 'user_id', (User,)) @@ -75,7 +230,9 @@ def test_obj_or_id_obj(self): assert isinstance(user_id, int) assert user_id == 1 - def test_obj_or_id_obj_no_id(self): + def test_obj_or_id_obj_no_id(self, m): + register_uris({'user': ['course_nickname']}, m) + nick = self.canvas.get_course_nickname(1) with self.assertRaises(TypeError): diff --git a/tests/util.py b/tests/util.py index f01befab..68fc613f 100644 --- a/tests/util.py +++ b/tests/util.py @@ -2,35 +2,46 @@ import requests_mock +from tests import settings -def register_uris(base_url, requirements, adapter): + +def register_uris(requirements, requests_mocker): """ - Given a list of required fixtures and an adapter object, register each fixture - as a uri with the adapter. + Given a list of required fixtures and an requests_mocker object, + register each fixture as a uri with the mocker. :param base_url: str :param requirements: dict - :param adapter: requests_mock.Adapter + :param requests_mocker: requests_mock.mocker.Mocker """ for fixture, objects in requirements.iteritems(): try: - data = json.loads(open('tests/fixtures/%s.json' % (fixture)).read()) + data = json.loads(open('tests/fixtures/{}.json'.format(fixture)).read()) except: - raise ValueError('Fixture %s.json contains invalid JSON.' % (fixture)) + raise ValueError('Fixture {}.json contains invalid JSON.'.format(fixture)) + + if not isinstance(objects, list): + raise TypeError('{} is not a list.'.format(objects)) + + for obj_name in objects: + obj = data.get(obj_name) - for obj in objects: - obj = data.get(obj) + if obj is None: + raise ValueError('{} does not exist in {}.json'.format( + obj_name.__repr__(), + fixture + )) method = requests_mock.ANY if obj['method'] == 'ANY' else obj['method'] - url = requests_mock.ANY if obj['endpoint'] == 'ANY' else base_url + obj['endpoint'] + url = requests_mock.ANY if obj['endpoint'] == 'ANY' else settings.BASE_URL + obj['endpoint'] try: - adapter.register_uri( + requests_mocker.register_uri( method, url, json=obj.get('data'), - status_code=obj.get('status_code'), + status_code=obj.get('status_code', 200), headers=obj.get('headers', {}) ) except Exception as e: diff --git a/tests_requirements.txt b/tests_requirements.txt index b9609a58..1261c908 100644 --- a/tests_requirements.txt +++ b/tests_requirements.txt @@ -1,4 +1,4 @@ -r requirements.txt coverage==4.1 -requests-mock==1.0.0 +requests-mock==1.2.0