Repository: ucfopen/canvasapi (language: python)

Issue:
New Quizzes
**What resource needs additional coverage?**

New Quizzes

**What endpoints need to be covered?**

- [x] [Get a new quiz](https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.show)
- [x] [List new quizzes](https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.index)
- [x] [Create a new quiz](https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.create)
- [x] [Update a single quiz](https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.update)
- [x] [Delete a new quiz](https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.destroy)

**Note:** These endpoints follow a different base URL than most other endpoints (`/api/quiz/v1/` instead of `/api/v1/`)

Related Issue: #609 


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00d1eb34..cff78414 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## [Unreleased]
 
+### New Endpoint Coverage
+
+- New Quizzes
+
 ### General
 
 - Added support for pagination with metadata when headers are missing (Thanks, [@bennettscience](https://github.com/bennettscience))
diff --git a/canvasapi/course.py b/canvasapi/course.py
index 0c3e227c..5c2bb0d6 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -24,6 +24,7 @@
 from canvasapi.grading_standard import GradingStandard
 from canvasapi.license import License
 from canvasapi.module import Module
+from canvasapi.new_quiz import NewQuiz
 from canvasapi.outcome_import import OutcomeImport
 from canvasapi.page import Page
 from canvasapi.paginated_list import PaginatedList
@@ -452,6 +453,29 @@ def create_module(self, module, **kwargs):
 
         return Module(self._requester, module_json)
 
+    def create_new_quiz(self, **kwargs):
+        """
+        Create a new quiz for the course.
+
+        :calls: `POST /api/quiz/v1/courses/:course_id/quizzes \
+        <https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.create>`_
+
+        :returns: The newly-created New Quiz object
+        :rtype: :class:`canvasapi.new_quiz.NewQuiz`
+        """
+        endpoint = "courses/{}/quizzes".format(self.id)
+
+        response = self._requester.request(
+            "POST",
+            endpoint,
+            _url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
+            _kwargs=combine_kwargs(**kwargs),
+        )
+        response_json = response.json()
+        response_json.update({"course_id": self.id})
+
+        return NewQuiz(self._requester, response_json)
+
     def create_page(self, wiki_page, **kwargs):
         """
         Create a new wiki page.
@@ -1701,6 +1725,56 @@ def get_multiple_submissions(self, **kwargs):
             _kwargs=combine_kwargs(**kwargs),
         )
 
+    def get_new_quiz(self, assignment, **kwargs):
+        """
+        Get details about a single new quiz.
+
+        :calls: `GET /api/quiz/v1/courses/:course_id/quizzes/:assignment_id \
+        <https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.show>`_
+
+        :param assignment: The id of the assignment associated with the quiz.
+        :type assignment: :class:`canvasapi.assignment.Assignment`
+            or :class:`canvasapi.new_quiz.NewQuiz` or int
+
+        :returns: A New Quiz object.
+        :rtype: :class:`canvasapi.new_quiz.NewQuiz`
+        """
+
+        assignment_id = obj_or_id(assignment, "assignment", (Assignment, NewQuiz))
+        endpoint = "courses/{}/quizzes/{}".format(self.id, assignment_id)
+
+        response = self._requester.request(
+            "GET",
+            endpoint,
+            _url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
+            _kwargs=combine_kwargs(**kwargs),
+        )
+        response_json = response.json()
+        response_json.update({"course_id": self.id})
+
+        return NewQuiz(self._requester, response_json)
+
+    def get_new_quizzes(self, **kwargs):
+        """
+        Get a list of new quizzes.
+
+        :calls: `GET /api/quiz/v1/courses/:course_id/quizzes \
+        <https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.index>`_
+
+        :returns: A paginated list of New Quiz objects.
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList`
+            of :class:`canvasapi.new_quiz.NewQuiz`
+        """
+        endpoint = "courses/{}/quizzes".format(self.id)
+        return PaginatedList(
+            NewQuiz,
+            self._requester,
+            "GET",
+            endpoint,
+            _url_override=self._requester.original_url + "/api/quiz/v1/" + endpoint,
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
     def get_outcome_group(self, group, **kwargs):
         """
         Returns the details of the Outcome Group with the given id.
@@ -1719,7 +1793,9 @@ def get_outcome_group(self, group, **kwargs):
         outcome_group_id = obj_or_id(group, "group", (OutcomeGroup,))
 
         response = self._requester.request(
-            "GET", "courses/{}/outcome_groups/{}".format(self.id, outcome_group_id)
+            "GET",
+            "courses/{}/outcome_groups/{}".format(self.id, outcome_group_id),
+            _kwargs=combine_kwargs(**kwargs),
         )
 
         return OutcomeGroup(self._requester, response.json())
diff --git a/canvasapi/new_quiz.py b/canvasapi/new_quiz.py
new file mode 100644
index 00000000..69200154
--- /dev/null
+++ b/canvasapi/new_quiz.py
@@ -0,0 +1,53 @@
+from canvasapi.canvas_object import CanvasObject
+from canvasapi.util import combine_kwargs
+
+
+class NewQuiz(CanvasObject):
+    def __str__(self):
+        return "{} ({})".format(self.title, self.id)
+
+    def delete(self, **kwargs):
+        """
+        Delete a single new quiz.
+
+        :calls: `DELETE /api/quiz/v1/courses/:course_id/quizzes/:assignment_id \
+        <https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.destroy>`_
+
+        :returns: The deleted New Quiz object
+        :rtype: :class:`canvasapi.new_quiz.NewQuiz`
+        """
+        endpoint = "courses/{}/quizzes/{}".format(self.course_id, self.id)
+
+        response = self._requester.request(
+            "DELETE",
+            endpoint,
+            _url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
+            _kwargs=combine_kwargs(**kwargs),
+        )
+        response_json = response.json()
+        response_json.update({"course_id": self.course_id})
+
+        return NewQuiz(self._requester, response_json)
+
+    def update(self, **kwargs):
+        """
+        Update a single New Quiz for the course.
+
+        :calls: `PATCH /api/quiz/v1/courses/:course_id/quizzes/:assignment_id \
+        <https://canvas.instructure.com/doc/api/new_quizzes.html#method.new_quizzes/quizzes_api.update>`_
+
+        :returns: The updated New Quiz object
+        :rtype: :class:`canvasapi.new_quiz.NewQuiz`
+        """
+        endpoint = "courses/{}/quizzes/{}".format(self.course_id, self.id)
+
+        response = self._requester.request(
+            "PATCH",
+            endpoint,
+            _url=self._requester.original_url + "/api/quiz/v1/" + endpoint,
+            _kwargs=combine_kwargs(**kwargs),
+        )
+        response_json = response.json()
+        response_json.update({"course_id": self.course_id})
+
+        return NewQuiz(self._requester, response_json)
diff --git a/canvasapi/paginated_list.py b/canvasapi/paginated_list.py
index c568874b..9decf747 100644
--- a/canvasapi/paginated_list.py
+++ b/canvasapi/paginated_list.py
@@ -25,6 +25,7 @@ def __init__(
         first_url,
         extra_attribs=None,
         _root=None,
+        _url_override=None,
         **kwargs
     ):
         self._elements = list()
@@ -39,6 +40,7 @@ def __init__(
         self._extra_attribs = extra_attribs or {}
         self._request_method = request_method
         self._root = _root
+        self._url_override = _url_override
 
     def __iter__(self):
         for element in self._elements:
@@ -53,7 +55,10 @@ def __repr__(self):
 
     def _get_next_page(self):
         response = self._requester.request(
-            self._request_method, self._next_url, **self._next_params
+            self._request_method,
+            self._next_url,
+            _url=self._url_override,
+            **self._next_params,
         )
         data = response.json()
         self._next_url = None


Test patch:
diff --git a/tests/fixtures/new_quiz.json b/tests/fixtures/new_quiz.json
new file mode 100644
index 00000000..5f6432e7
--- /dev/null
+++ b/tests/fixtures/new_quiz.json
@@ -0,0 +1,59 @@
+{
+	"create_new_quiz": {
+		"method": "POST",
+		"endpoint": "courses/1/quizzes",
+		"data": {
+			"id": 1,
+			"title": "New Quiz One",
+			"instructions": "<p>This is the first New Quiz. Good luck!</p>"
+		},
+		"status_code": 200
+	},
+	"delete_new_quiz": {
+		"method": "DELETE",
+		"endpoint": "courses/1/quizzes/1",
+		"data": {
+			"id": 1,
+			"title": "New Quiz One",
+			"instructions": "<p>This is the first New Quiz. Good luck!</p>"
+		},
+		"status_code": 200
+	},
+	"get_new_quiz": {
+		"method": "GET",
+		"endpoint": "courses/1/quizzes/1",
+		"data": {
+			"id": 1,
+			"title": "New Quiz One",
+			"instructions": "<p>This is the first New Quiz. Good luck!</p>"
+		},
+		"status_code": 200
+	},
+	"get_new_quizzes": {
+		"method": "GET",
+		"endpoint": "courses/1/quizzes",
+		"data": [
+			{
+				"id": 1,
+				"title": "New Quiz One",
+				"instructions": "<p>This is the first New Quiz. Good luck!</p>"
+			},
+			{
+				"id": 2,
+				"title": "New Quiz Two",
+				"instructions": "<p>This is the second New Quiz. Good luck!</p>"
+			}
+		],
+		"status_code": 200
+	},
+	"update_new_quiz": {
+		"method": "PATCH",
+		"endpoint": "courses/1/quizzes/1",
+		"data": {
+			"id": 1,
+			"title": "New Quiz One - Updated!",
+			"instructions": "<p>This is the updated New Quiz. You got this!</p>"
+		},
+		"status_code": 200
+	}
+}
diff --git a/tests/settings.py b/tests/settings.py
index a817f9f1..7a017a9c 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -1,5 +1,6 @@
 BASE_URL = "https://example.com"
 BASE_URL_GRAPHQL = "https://example.com/api/"
+BASE_URL_NEW_QUIZZES = "https://example.com/api/quiz/v1/"
 BASE_URL_WITH_VERSION = "https://example.com/api/v1/"
 BASE_URL_AS_HTTP = "http://example.com"
 BASE_URL_AS_BLANK = ""
diff --git a/tests/test_course.py b/tests/test_course.py
index 21b483bb..214bafb2 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -35,6 +35,7 @@
 from canvasapi.group import Group, GroupCategory
 from canvasapi.license import License
 from canvasapi.module import Module
+from canvasapi.new_quiz import NewQuiz
 from canvasapi.outcome import OutcomeGroup, OutcomeLink, OutcomeResult
 from canvasapi.outcome_import import OutcomeImport
 from canvasapi.paginated_list import PaginatedList
@@ -333,6 +334,23 @@ def test_create_quiz_fail(self, m):
         with self.assertRaises(RequiredFieldMissing):
             self.course.create_quiz({})
 
+    # create_new_quiz()
+    def test_create_new_quiz(self, m):
+        register_uris(
+            {"new_quiz": ["create_new_quiz"]},
+            m,
+            base_url=settings.BASE_URL_NEW_QUIZZES,
+        )
+
+        title = "New Quiz One"
+        new_new_quiz = self.course.create_new_quiz(quiz={"title": title})
+
+        self.assertIsInstance(new_new_quiz, NewQuiz)
+        self.assertTrue(hasattr(new_new_quiz, "title"))
+        self.assertEqual(new_new_quiz.title, title)
+        self.assertTrue(hasattr(new_new_quiz, "course_id"))
+        self.assertEqual(new_new_quiz.course_id, self.course.id)
+
     # get_quiz()
     def test_get_quiz(self, m):
         register_uris({"course": ["get_quiz"]}, m)
@@ -390,6 +408,38 @@ def test_get_quizzes(self, m):
         self.assertTrue(hasattr(quiz_list[0], "course_id"))
         self.assertEqual(quiz_list[0].course_id, self.course.id)
 
+    # get_new_quiz()
+    def test_get_new_quiz(self, m):
+        register_uris(
+            {"new_quiz": ["get_new_quiz"]},
+            m,
+            base_url=settings.BASE_URL_NEW_QUIZZES,
+        )
+
+        new_quiz = self.course.get_new_quiz(1)
+
+        self.assertIsInstance(new_quiz, NewQuiz)
+        self.assertTrue(hasattr(new_quiz, "title"))
+        self.assertEqual(new_quiz.title, "New Quiz One")
+        self.assertTrue(hasattr(new_quiz, "course_id"))
+        self.assertEqual(new_quiz.course_id, self.course.id)
+
+    # get_new_quizzes()
+    def test_get_new_quizzes(self, m):
+        register_uris(
+            {"new_quiz": ["get_new_quizzes"]},
+            m,
+            base_url=settings.BASE_URL_NEW_QUIZZES,
+        )
+
+        new_quizzes = self.course.get_new_quizzes()
+        new_quiz_list = list(new_quizzes)
+
+        self.assertEqual(len(new_quiz_list), 2)
+        self.assertIsInstance(new_quiz_list[0], NewQuiz)
+        self.assertTrue(hasattr(new_quiz_list[0], "title"))
+        self.assertEqual(new_quiz_list[0].title, "New Quiz One")
+
     # get_modules()
     def test_get_modules(self, m):
         register_uris({"course": ["list_modules", "list_modules2"]}, m)
diff --git a/tests/test_new_quiz.py b/tests/test_new_quiz.py
new file mode 100644
index 00000000..020533fa
--- /dev/null
+++ b/tests/test_new_quiz.py
@@ -0,0 +1,68 @@
+import unittest
+
+import requests_mock
+
+from canvasapi import Canvas
+from canvasapi.new_quiz import NewQuiz
+from tests import settings
+from tests.util import register_uris
+
+
+@requests_mock.Mocker()
+class TestNewQuiz(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        with requests_mock.Mocker() as m:
+            register_uris({"course": ["get_by_id"]}, m)
+            register_uris(
+                {"new_quiz": ["get_new_quiz"]},
+                m,
+                base_url=settings.BASE_URL_NEW_QUIZZES,
+            )
+
+            self.course = self.canvas.get_course(1)
+            self.new_quiz = self.course.get_new_quiz(1)
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.new_quiz)
+        self.assertIsInstance(string, str)
+
+    # delete()
+    def test_delete(self, m):
+        register_uris(
+            {"new_quiz": ["delete_new_quiz"]},
+            m,
+            base_url=settings.BASE_URL_NEW_QUIZZES,
+        )
+
+        deleted_quiz = self.new_quiz.delete()
+
+        self.assertIsInstance(deleted_quiz, NewQuiz)
+        self.assertTrue(hasattr(deleted_quiz, "title"))
+        self.assertEqual(deleted_quiz.title, "New Quiz One")
+        self.assertTrue(hasattr(deleted_quiz, "course_id"))
+        self.assertEqual(deleted_quiz.course_id, self.course.id)
+
+    # update()
+    def test_update(self, m):
+        register_uris(
+            {"new_quiz": ["update_new_quiz"]},
+            m,
+            base_url=settings.BASE_URL_NEW_QUIZZES,
+        )
+
+        new_title = "New Quiz One - Updated!"
+        new_instructions = "<p>This is the updated New Quiz. You got this!</p>"
+        new_quiz = self.new_quiz.update(
+            quiz={"title": new_title, "instructions": new_instructions}
+        )
+
+        self.assertIsInstance(new_quiz, NewQuiz)
+        self.assertTrue(hasattr(new_quiz, "title"))
+        self.assertEqual(new_quiz.title, new_title)
+        self.assertTrue(hasattr(new_quiz, "instructions"))
+        self.assertEqual(new_quiz.instructions, new_instructions)
+        self.assertTrue(hasattr(new_quiz, "course_id"))
+        self.assertEqual(new_quiz.course_id, self.course.id)