Repository: ucfopen/canvasapi

Issue:
Favorite object removal does not handle parameters (from response json data) correctly
# Describe the bug

_Favorite_ objects created using the `add_favorite_course` or `add_favorite_group` methods of the _CurrentUser_ class acquire a _context_type_ parameter that is capitalized (i.e. "Course" or "Group"). The `remove` method of the _Favorite_ class then makes use of this _context_type_ erroneously, testing for equality with _lowercase_ strings "course" or "group" (in the file canvasapi.favorite). As a result, the variable `uri_str` is not defined as it should be.

# To Reproduce

Steps to reproduce the behavior:

1. Instantiate a `canvasapi.canvas.Canvas` object using auth token: `c = Canvas(<base_url>, <token>)`
2. Acquire the CurrentUser object: `user = c.get_current_user()`
3. Use any valid "course_id" with the CurrentUser method `add_favorite_course` to acquire a Favorite object: `f = user.add_favorite_course(<course_id>)` (this will result in an object with `context_type` of "Course" (with a capital C)
4. Call the `remove` method of this Favorite object: `f.remove()` (results in an error because neither conditional was true, and so `uri_str` is undefined).

# Expected behavior

Course (or group) should be removed from favorites with no error.

# Environment information

- Python version: 3.8.1
- CanvasAPI version: 2.1.0

# Additional context

Canvas's HTTP API is presumably going to continue returning capitalized versions of "Course" and "Group" as their "context_type". Therefore, this issue could be fixed in in the file canvasapi.favorite (in the `remove` method) by:
- Comparing `self.context_type` with capitalized versions of "Course" and "Group"
- Adding the string `.lower()` method to `self.context_type` (to coerce the strings to lowercase for comparison).

Thank you! I have appreciated your package a lot!

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/36102394/badge)](https://coveralls.io/builds/36102394)

Coverage remained the same at 100.0% when pulling **1eb52c72216faf59e3f8dc7c08b95cdbc5c825a8 on deundrewilliams:develop** into **6b9f09183d1c19bd6fb9b0fe91771ca50315551f on ucfopen:develop**.

jessemcbride: Hey Deundre,

This patch looks good so far, but let's add some regression tests to it to make sure this behavior works correctly. We have [two existing tests](https://github.com/ucfopen/canvasapi/blob/develop/tests/test_favorite.py#L39-L53) that are currently passing because they [receive a lowercased context type from our API fixture](https://github.com/ucfopen/canvasapi/blob/develop/tests/fixtures/favorite.json). We should add additional fixtures that return capitalized variants of `Group` and `Course` and test that a call to `favorite.remove()` returns successfully. 
deundrewilliams: Hello,

I added the new fixtures and tests that return the capitalized variants and made sure that the methods still passed.
deundrewilliams: I think decoupling the capitalized and uncapitalized codes is a good idea @jessemcbride. I'll start working on that now.
Thetwam: It looks like the `flake8` and `black` checks are failing.

Running `black canvasapi tests` should do the trick.

Patch:
diff --git a/canvasapi/favorite.py b/canvasapi/favorite.py
index a436ca7..0eefab7 100644
--- a/canvasapi/favorite.py
+++ b/canvasapi/favorite.py
@@ -17,11 +17,11 @@ class Favorite(CanvasObject):
 
         :rtype: :class:`canvasapi.favorite.Favorite`
         """
-        if self.context_type == "course":
+        if self.context_type.lower() == "course":
             id = self.context_id
             uri_str = "users/self/favorites/courses/{}"
 
-        elif self.context_type == "group":
+        elif self.context_type.lower() == "group":
             id = self.context_id
             uri_str = "users/self/favorites/groups/{}"
 


Test patch:
diff --git a/tests/fixtures/current_user.json b/tests/fixtures/current_user.json
index 7c5b998..80cf284 100644
--- a/tests/fixtures/current_user.json
+++ b/tests/fixtures/current_user.json
@@ -8,6 +8,16 @@
 				"context_type": "course"
 			},
 		"status_code": 200
+    },
+    "add_favorite_course_cap_context_type": {
+		"method": "POST",
+		"endpoint": "users/self/favorites/courses/1",
+		"data":
+			{
+				"context_id": 1,
+				"context_type": "Course"
+			},
+		"status_code": 200
 	},
 	"add_favorite_course_by_sis_id": {
 		"method": "POST",
@@ -28,6 +38,16 @@
 				"context_type": "group"
 			},
 		"status_code": 200
+    },
+    "add_favorite_group_cap_context_type": {
+		"method": "POST",
+		"endpoint": "users/self/favorites/groups/1",
+		"data":
+			{
+				"context_id": 1,
+				"context_type": "Group"
+			},
+		"status_code": 200
 	},
 	"add_favorite_group_by_sis_id": {
 		"method": "POST",
@@ -122,6 +142,16 @@
 			"context_type": "course"
 		},
 		"status_code": 200
+    },
+    "remove_favorite_course_cap_context_type": {
+		"method": "DELETE",
+		"endpoint": "users/self/favorites/courses/1",
+		"data":
+		{
+			"context_id": 1,
+			"context_type": "Course"
+		},
+		"status_code": 200
 	},
 	"remove_favorite_group": {
 		"method": "DELETE",
@@ -132,6 +162,16 @@
 			"context_type": "group"
 		},
 		"status_code": 200
+    },
+    "remove_favorite_group_cap_context_type": {
+		"method": "DELETE",
+		"endpoint": "users/self/favorites/groups/1",
+		"data":
+		{
+			"context_id": 1,
+			"context_type": "Group"
+		},
+		"status_code": 200
 	},
 	"reset_favorite_courses": {
 		"method": "DELETE",
diff --git a/tests/test_favorite.py b/tests/test_favorite.py
index 7a49114..6b30759 100644
--- a/tests/test_favorite.py
+++ b/tests/test_favorite.py
@@ -16,38 +16,133 @@ class TestFavorite(unittest.TestCase):
         with requests_mock.Mocker() as m:
             requires = {
                 "current_user": [
-                    "add_favorite_course",
-                    "add_favorite_group",
                     "get_by_id",
                 ]
             }
             register_uris(requires, m)
 
             self.user = self.canvas.get_current_user()
-            self.favorite_course = self.user.add_favorite_course(1)
-            self.favorite_group = self.user.add_favorite_group(1)
 
     # __str__()
-    def test__str__(self, m):
-        string = str(self.favorite_course)
+    def test_uncapitalized___str__(self, m):
+
+        register_uris(
+            {
+                "current_user": [
+                    "add_favorite_course",
+                    "add_favorite_group",
+                ]
+            },
+            m,
+        )
+
+        favorite_course = self.user.add_favorite_course(1)
+        favorite_group = self.user.add_favorite_group(1)
+
+        string = str(favorite_course)
+        self.assertIsInstance(string, str)
+
+        string = str(favorite_group)
+        self.assertIsInstance(string, str)
+
+    def test_capitalized___str__(self, m):
+
+        register_uris(
+            {
+                "current_user": [
+                    "add_favorite_course_cap_context_type",
+                    "add_favorite_group_cap_context_type",
+                ]
+            },
+            m,
+        )
+
+        favorite_course = self.user.add_favorite_course(1)
+        favorite_group = self.user.add_favorite_group(1)
+
+        string = str(favorite_course)
         self.assertIsInstance(string, str)
 
-        string = str(self.favorite_group)
+        string = str(favorite_group)
         self.assertIsInstance(string, str)
 
     # remove()
-    def test_remove_favorite_course(self, m):
-        register_uris({"current_user": ["remove_favorite_course"]}, m)
+    def test_remove_uncapitalized_favorite_course(self, m):
+
+        register_uris(
+            {
+                "current_user": [
+                    "add_favorite_course",
+                    "remove_favorite_course",
+                ]
+            },
+            m,
+        )
+
+        favorite_course = self.user.add_favorite_course(1)
+
+        evnt = favorite_course.remove()
 
-        evnt = self.favorite_course.remove()
         self.assertIsInstance(evnt, Favorite)
         self.assertEqual(evnt.context_type, "course")
         self.assertEqual(evnt.context_id, 1)
 
-    def test_remove_favorite_group(self, m):
-        register_uris({"current_user": ["remove_favorite_group"]}, m)
+    def test_remove_uncapitalized_favorite_group(self, m):
+
+        register_uris(
+            {
+                "current_user": [
+                    "add_favorite_group",
+                    "remove_favorite_group",
+                ]
+            },
+            m,
+        )
+
+        favorite_group = self.user.add_favorite_group(1)
+
+        evnt = favorite_group.remove()
 
-        evnt = self.favorite_group.remove()
         self.assertIsInstance(evnt, Favorite)
         self.assertEqual(evnt.context_type, "group")
         self.assertEqual(evnt.context_id, 1)
+
+    def test_remove_capitalized_favorite_course(self, m):
+
+        register_uris(
+            {
+                "current_user": [
+                    "add_favorite_course_cap_context_type",
+                    "remove_favorite_course_cap_context_type",
+                ]
+            },
+            m,
+        )
+
+        favorite_course = self.user.add_favorite_course(1)
+
+        evnt = favorite_course.remove()
+
+        self.assertIsInstance(evnt, Favorite)
+        self.assertEqual(evnt.context_type, "Course")
+        self.assertEqual(evnt.context_id, 1)
+
+    def test_remove_capitalized_favorite_group(self, m):
+
+        register_uris(
+            {
+                "current_user": [
+                    "add_favorite_group_cap_context_type",
+                    "remove_favorite_group_cap_context_type",
+                ]
+            },
+            m,
+        )
+
+        favorite_group = self.user.add_favorite_group(1)
+
+        evnt = favorite_group.remove()
+
+        self.assertIsInstance(evnt, Favorite)
+        self.assertEqual(evnt.context_type, "Group")
+        self.assertEqual(evnt.context_id, 1)