Repository: ucfopen/canvasapi

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:
codecov[bot]: ## [Codecov](https://codecov.io/gh/ucfopen/canvasapi/pull/612?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen) Report
> Merging [#612](https://codecov.io/gh/ucfopen/canvasapi/pull/612?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen) (7a6ce47) into [develop](https://codecov.io/gh/ucfopen/canvasapi/commit/5a576237c705823ee1b96759ba777dddb09de365?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen) (5a57623) will **decrease** coverage by `0.09%`.
> The diff coverage is `83.33%`.

```diff
@@             Coverage Diff             @@
##           develop     #612      +/-   ##
===========================================
- Coverage   100.00%   99.91%   -0.09%     
===========================================
  Files           72       73       +1     
  Lines         3684     3702      +18     
===========================================
+ Hits          3684     3699      +15     
- Misses           0        3       +3     
```


| [Impacted Files](https://codecov.io/gh/ucfopen/canvasapi/pull/612?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen) | Coverage Δ | |
|---|---|---|
| [canvasapi/course.py](https://codecov.io/gh/ucfopen/canvasapi/pull/612?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen#diff-Y2FudmFzYXBpL2NvdXJzZS5weQ==) | `99.46% <76.92%> (-0.54%)` | :arrow_down: |
| [canvasapi/new\_quiz.py](https://codecov.io/gh/ucfopen/canvasapi/pull/612?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen#diff-Y2FudmFzYXBpL25ld19xdWl6LnB5) | `100.00% <100.00%> (ø)` | |
| [canvasapi/paginated\_list.py](https://codecov.io/gh/ucfopen/canvasapi/pull/612?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen#diff-Y2FudmFzYXBpL3BhZ2luYXRlZF9saXN0LnB5) | `100.00% <100.00%> (ø)` | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ucfopen)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00d1eb3..cff7841 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 0c3e227..5c2bb0d 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -24,6 +24,7 @@ from canvasapi.grading_period import GradingPeriod
 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 @@ class Course(CanvasObject):
 
         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 @@ class Course(CanvasObject):
             _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 @@ class Course(CanvasObject):
         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 0000000..6920015
--- /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 c568874..9decf74 100644
--- a/canvasapi/paginated_list.py
+++ b/canvasapi/paginated_list.py
@@ -25,6 +25,7 @@ class PaginatedList(object):
         first_url,
         extra_attribs=None,
         _root=None,
+        _url_override=None,
         **kwargs
     ):
         self._elements = list()
@@ -39,6 +40,7 @@ class PaginatedList(object):
         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 @@ class PaginatedList(object):
 
     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 0000000..5f6432e
--- /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 a817f9f..7a017a9 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 21b483b..214bafb 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -35,6 +35,7 @@ from canvasapi.grading_standard import GradingStandard
 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 @@ class TestCourse(unittest.TestCase):
         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 @@ class TestCourse(unittest.TestCase):
         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 0000000..020533f
--- /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)