Repository: SebRut/pygrocy (python)

Issue:
change assertions in test to unittest ones
as noted in #72 currently most assertions arent being done with the assertions of the unittest package. All assertions should get replaced by the assertions available in this package.

Pull request:
Rework tests
## Description

--Your description here--

**Related issue (if applicable):** fixes #76 #56 #55 

## Checklist
  - [x] The code change is tested and works locally.
  - [x] tests pass. **Your PR won't be merged unless tests pass**
  - [x] There is no commented out code in this PR


Patch:
diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py
index fcc8101..c826290 100644
--- a/pygrocy/grocy.py
+++ b/pygrocy/grocy.py
@@ -30,11 +30,10 @@ class Product(object):
 
     def get_details(self, api_client: GrocyApiClient):
         details = api_client.get_product(self.id)
-        if details is None:
-            return
-        self._name = details.product.name
-        self._barcodes = details.product.barcodes
-        self._product_group_id = details.product.product_group_id
+        if details:
+            self._name = details.product.name
+            self._barcodes = details.product.barcodes
+            self._product_group_id = details.product.product_group_id
 
     @property
     def name(self) -> str:
@@ -163,9 +162,7 @@ class Grocy(object):
 
     def stock(self) -> List[Product]:
         raw_stock = self._api_client.get_stock()
-
         stock = [Product(resp) for resp in raw_stock]
-
         return stock
 
     def volatile_stock(self) -> CurrentVolatilStockResponse:
@@ -173,8 +170,6 @@ class Grocy(object):
 
     def expiring_products(self, get_details: bool = False) -> List[Product]:
         raw_expiring_product = self.volatile_stock().expiring_products
-        if raw_expiring_product is None:
-            return
         expiring_product = [Product(resp) for resp in raw_expiring_product]
 
         if get_details:
@@ -184,8 +179,6 @@ class Grocy(object):
 
     def expired_products(self, get_details: bool = False) -> List[Product]:
         raw_expired_product = self.volatile_stock().expired_products
-        if raw_expired_product is None:
-            return
         expired_product = [Product(resp) for resp in raw_expired_product]
 
         if get_details:
@@ -196,8 +189,6 @@ class Grocy(object):
 
     def missing_products(self, get_details: bool = False) -> List[Product]:
         raw_missing_product = self.volatile_stock().missing_products
-        if raw_missing_product is None:
-            return
         missing_product = [Product(resp) for resp in raw_missing_product]
 
         if get_details:
@@ -258,8 +249,7 @@ class Grocy(object):
         return [Group(resp) for resp in raw_groups]
         
     def add_product_pic(self, product_id: int, pic_path: str):
-        if self._api_client.upload_product_picture(product_id, pic_path).status_code != 204:
-            return
+        self._api_client.upload_product_picture(product_id, pic_path)
         return self._api_client.update_product_pic(product_id)
         
     def get_userfields(self, entity: str, object_id: int):
diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py
index cf717ad..13af21a 100644
--- a/pygrocy/grocy_api_client.py
+++ b/pygrocy/grocy_api_client.py
@@ -399,7 +399,7 @@ class GrocyApiClient(object):
         if done_by is not None:
             data["done_by"] = done_by
 
-        self._do_post_request(f"chores/{chore_id}/execute", data)
+        return self._do_post_request(f"chores/{chore_id}/execute", data)
 
     def add_product(self, product_id, amount: float, price: float, best_before_date: datetime = None,
                     transaction_type: TransactionType = TransactionType.PURCHASE):
@@ -412,7 +412,7 @@ class GrocyApiClient(object):
         if best_before_date is not None:
             data["best_before_date"] = best_before_date.strftime('%Y-%m-%d')
 
-        self._do_post_request(f"stock/products/{product_id}/add", data)
+        return self._do_post_request(f"stock/products/{product_id}/add", data)
 
     def consume_product(self, product_id: int, amount: float = 1, spoiled: bool = False,
                         transaction_type: TransactionType = TransactionType.CONSUME):
@@ -463,8 +463,6 @@ class GrocyApiClient(object):
         return [LocationData(response) for response in parsed_json]
 
     def upload_product_picture(self, product_id: int, pic_path: str):
-        if not os.path.exists(pic_path):
-            return
         b64fn = base64.b64encode('{}.jpg'.format(product_id).encode('ascii'))
         req_url = "files/productpictures/" + str(b64fn, "utf-8")
         with open(pic_path,'rb') as pic:


Test patch:
diff --git a/test/test_grocy.py b/test/test_grocy.py
index 8251b48..4671b67 100644
--- a/test/test_grocy.py
+++ b/test/test_grocy.py
@@ -1,37 +1,78 @@
 from unittest import TestCase
 from unittest.mock import patch, mock_open
 
+import json
 from datetime import datetime
 from requests.exceptions import HTTPError
 import responses
 from pygrocy import Grocy
-from pygrocy.grocy import Product
-from pygrocy.grocy import Group
-from pygrocy.grocy import ShoppingListProduct
-from pygrocy.grocy_api_client import CurrentStockResponse, GrocyApiClient
+from pygrocy.grocy import Chore, Product, Group, ShoppingListProduct
+
+from pygrocy.grocy_api_client import CurrentStockResponse, GrocyApiClient, TransactionType, UserDto, ChoreDetailsResponse, ProductData
 from test.test_const import CONST_BASE_URL, CONST_PORT, CONST_SSL
 
 class TestGrocy(TestCase):
 
     def setUp(self):
-        self.grocy = Grocy(CONST_BASE_URL, "api_key")
-        self.grocy = None
+        self.grocy_regular = Grocy(CONST_BASE_URL, "api_key")
         self.grocy = Grocy(CONST_BASE_URL, "demo_mode",  verify_ssl = CONST_SSL, port = CONST_PORT)
+        self.base_url = f"{CONST_BASE_URL}:{CONST_PORT}/api"
+        self.date_test = datetime.strptime("2019-05-04 11:31:04", '%Y-%m-%d %H:%M:%S')
 
     def test_init(self):
-        assert isinstance(self.grocy, Grocy)
+        self.assertIsInstance(self.grocy, Grocy)
         
     def test_get_chores_valid(self):
         chores = self.grocy.chores(get_details=True)
         
-        assert isinstance(chores, list)
-        self.assertEqual(len(chores), 6)
-        assert chores[0].id == 1
-        assert chores[1].id == 2
-        assert chores[2].id == 3
-        assert chores[3].id == 4
-        assert chores[4].id == 5
-        assert chores[5].id == 6
+        self.assertIsInstance(chores, list)
+        self.assertGreaterEqual(len(chores), 1)
+        for chore in chores:
+            self.assertIsInstance(chore, Chore)
+            self.assertIsInstance(chore.id, int)
+            self.assertIsInstance(chore.last_tracked_time, datetime)
+            self.assertIsInstance(chore.next_estimated_execution_time, datetime)
+            self.assertIsInstance(chore.name, str)
+            self.assertIsInstance(chore.last_done_by, UserDto)
+
+    @responses.activate
+    def test_get_chore_details_valid(self):
+        details_json = """{
+            "chore": {
+                "id": "1",
+                "name": "Changed towels in the bathroom",
+                "description": null,
+                "period_type": "manually",
+                "period_days": "5",
+                "row_created_timestamp": "2020-03-16 00:50:14",
+                "period_config": null,
+                "track_date_only": "0",
+                "rollover": "0",
+                "assignment_type": null,
+                "assignment_config": null,
+                "next_execution_assigned_to_user_id": null,
+                "consume_product_on_execution": "0",
+                "product_id": null,
+                "product_amount": null,
+                "period_interval": "1"
+            },
+            "last_tracked": "2020-03-11 00:50:15",
+            "tracked_count": 3,
+            "last_done_by": {
+                "id": "1",
+                "username": "Demo User",
+                "first_name": null,
+                "last_name": null,
+                "row_created_timestamp": "2020-03-16 00:50:13",
+                "display_name": "Demo User"
+            },
+            "next_estimated_execution_time": "2999-12-31 23:59:59",
+            "next_execution_assigned_user": null
+        }"""
+        details_json = json.loads(details_json)
+        responses.add(responses.GET, f"{self.base_url}/chores/1", json=details_json, status=200)
+        chore_details = self.grocy.chore(1)
+        self.assertIsInstance(chore_details, ChoreDetailsResponse)
 
     def test_product_get_details_valid(self):
         stock = self.grocy.stock()
@@ -41,66 +82,67 @@ class TestGrocy(TestCase):
         api_client = GrocyApiClient(CONST_BASE_URL, "demo_mode", port = CONST_PORT, verify_ssl = CONST_SSL)
         product.get_details(api_client)
 
-        assert isinstance(product.name, str)
-        assert isinstance(product.product_group_id, int)
+        self.assertIsInstance(product.name, str)
+        self.assertIsInstance(product.id, int)
+        self.assertIsInstance(product.available_amount, float)
+        self.assertIsInstance(product.best_before_date, datetime)
+        if product.barcodes:
+            self.assertIsInstance(product.barcodes, (list, str))
+        self.assertIsInstance(product.product_group_id, int)
 
     @responses.activate
     def test_product_get_details_invalid_no_data(self):
-        current_stock_response = CurrentStockResponse({
-            "product_id": 0,
-            "amount": "0.33",
-            "best_before_date": "2019-05-02"
-        })
-
-        product = Product(current_stock_response)
-
-        api_client = GrocyApiClient(CONST_BASE_URL, "demo_mode", port = CONST_PORT, verify_ssl = CONST_SSL)
-
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/products/0", status=200)
-
-        product.get_details(api_client)
-
-        assert product.name is None
+        responses.add(responses.GET, f"{self.base_url}/stock/products/0", status=200)
+        product = self.grocy.product(0)
+        self.assertIsNone(product)
 
     def test_get_stock_valid(self):
         stock = self.grocy.stock()
 
-        assert isinstance(stock, list)
-        assert len(stock) >= 10
+        self.assertIsInstance(stock, list)
+        self.assertGreaterEqual(len(stock), 10)
         for prod in stock:
-            assert isinstance(prod, Product)
+            self.assertIsInstance(prod, Product)
 
     @responses.activate
     def test_get_stock_invalid_missing_data(self):
         resp = []
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/stock", json=resp, status=200)
         self.assertEqual(len(self.grocy.stock()) ,0)
         
     def test_get_shopping_list_valid(self):
         shopping_list = self.grocy.shopping_list(True)
         
-        assert isinstance(shopping_list, list)
-        assert len(shopping_list) >= 1
+        self.assertIsInstance(shopping_list, list)
+        self.assertGreaterEqual(len(shopping_list), 1)
         for item in shopping_list:
-            assert isinstance(item, ShoppingListProduct)
-            
+            self.assertIsInstance(item, ShoppingListProduct)
+            self.assertIsInstance(item.id, int)
+            if item.product_id:
+                self.assertIsInstance(item.product_id, int)
+                self.assertIsInstance(item.product, ProductData)
+                self.assertIsInstance(item.product.id, int)
+            self.assertIsInstance(item.amount, float)
+            if item.note:
+                self.assertIsInstance(item.note, str)
+
     @responses.activate
     def test_get_shopping_list_invalid_no_data(self):
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/objects/shopping_list", status=400)
+        responses.add(responses.GET, f"{self.base_url}/objects/shopping_list", status=400)
         self.assertRaises(HTTPError, self.grocy.shopping_list)
         
     @responses.activate
     def test_get_shopping_list_invalid_missing_data(self):
         resp = []
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/objects/shopping_list", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/objects/shopping_list", json=resp, status=200)
         self.assertEqual(len(self.grocy.shopping_list()), 0)
         
     def test_add_missing_product_to_shopping_list_valid(self):
-         assert self.grocy.add_missing_product_to_shopping_list() is None
+         self.assertIsNone(self.grocy.add_missing_product_to_shopping_list())
         
     @responses.activate
     def test_add_missing_product_to_shopping_list_error(self):
-        responses.add(responses.POST, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/shoppinglist/add-missing-products", status=400)
+        responses.add(responses.POST, f"{self.base_url}/stock/shoppinglist/add-missing-products", status=400)
         self.assertRaises(HTTPError, self.grocy.add_missing_product_to_shopping_list)
         
     def test_add_product_to_shopping_list_valid(self):
@@ -111,89 +153,87 @@ class TestGrocy(TestCase):
         
     @responses.activate
     def test_clear_shopping_list_valid(self):
-        responses.add(responses.POST, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/shoppinglist/clear", status=204)
+        responses.add(responses.POST, f"{self.base_url}/stock/shoppinglist/clear", status=204)
         self.grocy.clear_shopping_list()
         
     @responses.activate
     def test_clear_shopping_list_error(self):
-        responses.add(responses.POST, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/shoppinglist/clear", status=400)
+        responses.add(responses.POST, f"{self.base_url}/stock/shoppinglist/clear", status=400)
         self.assertRaises(HTTPError, self.grocy.clear_shopping_list)
         
     @responses.activate
     def test_remove_product_in_shopping_list_valid(self):
-        responses.add(responses.POST, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/shoppinglist/remove-product", status=204)
+        responses.add(responses.POST, f"{self.base_url}/stock/shoppinglist/remove-product", status=204)
         self.grocy.remove_product_in_shopping_list(1)
         
     @responses.activate
     def test_remove_product_in_shopping_list_error(self):
-        responses.add(responses.POST, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/shoppinglist/remove-product", status=400)
+        responses.add(responses.POST, f"{self.base_url}/stock/shoppinglist/remove-product", status=400)
         self.assertRaises(HTTPError, self.grocy.remove_product_in_shopping_list, 1)
         
     def test_get_product_groups_valid(self):
         product_groups_list = self.grocy.product_groups()
         
-        assert isinstance(product_groups_list, list)
-        assert len(product_groups_list) >= 1
-        for item in product_groups_list:
-            assert isinstance(item, Group)
+        self.assertIsInstance(product_groups_list, list)
+        self.assertGreaterEqual(len(product_groups_list), 1)
+        for group in product_groups_list:
+            self.assertIsInstance(group, Group)
+            self.assertIsInstance(group.id, int)
+            self.assertIsInstance(group.name, str)
+            if group.description:
+                self.assertIsInstance(group.description, str)
             
     @responses.activate
     def test_get_product_groups_invalid_no_data(self):
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/objects/product_groups", status=400)
+        responses.add(responses.GET, f"{self.base_url}/objects/product_groups", status=400)
         self.assertRaises(HTTPError, self.grocy.product_groups)
         
     @responses.activate
     def test_get_product_groups_invalid_missing_data(self):
         resp = []
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/objects/product_groups", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/objects/product_groups", json=resp, status=200)
         self.assertEqual(len(self.grocy.product_groups()), 0)
-        
+
     @responses.activate
-    def test_upload_product_picture_valid(self):
+    def test_add_product_pic_valid(self):
         with patch("os.path.exists" ) as m_exist:
             with patch("builtins.open", mock_open()) as m_open:
                 m_exist.return_value = True
-                api_client = GrocyApiClient(CONST_BASE_URL, "demo_mode", port = CONST_PORT, verify_ssl = CONST_SSL)
-                responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/files/productpictures/MS5qcGc=", status=204)
-                api_client.upload_product_picture(1,"/somepath/pic.jpg")
-            
+                responses.add(responses.PUT, f"{self.base_url}/files/productpictures/MS5qcGc=", status=204)
+                responses.add(responses.PUT, f"{self.base_url}/objects/products/1", status=204)
+                resp = self.grocy.add_product_pic(1,"/somepath/pic.jpg")
+                self.assertIsNone(resp)
+
     @responses.activate
-    def test_upload_product_picture_invalid_missing_data(self):
+    def test_add_product_pic_invalid_missing_data(self):
         with patch("os.path.exists" ) as m_exist:
             m_exist.return_value = False
-            api_client = GrocyApiClient(CONST_BASE_URL, "demo_mode", port = CONST_PORT, verify_ssl = CONST_SSL)
-            responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/files/productpictures/MS5qcGc=", status=204)
-            assert api_client.upload_product_picture(1,"/somepath/pic.jpg") is None
-        
+            self.assertRaises(FileNotFoundError, self.grocy.add_product_pic,1,"/somepath/pic.jpg")
+
     @responses.activate
     def test_upload_product_picture_error(self):
         with patch("os.path.exists" ) as m_exist:
             with patch("builtins.open", mock_open()) as m_open:
                 m_exist.return_value = True
                 api_client = GrocyApiClient(CONST_BASE_URL, "demo_mode", port = CONST_PORT, verify_ssl = CONST_SSL)
-                responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/files/productpictures/MS5qcGc=", status=400)
+                responses.add(responses.PUT, f"{self.base_url}/files/productpictures/MS5qcGc=", status=400)
                 self.assertRaises(HTTPError, api_client.upload_product_picture, 1,"/somepath/pic.jpg")
                 
-    @responses.activate
-    def test_update_product_pic_valid(self):
-        api_client = GrocyApiClient(CONST_BASE_URL, "demo_mode", port = CONST_PORT, verify_ssl = CONST_SSL)
-        responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/objects/products/1", status=204)
-        api_client.update_product_pic(1)
         
     @responses.activate
     def test_update_product_pic_error(self):
         api_client = GrocyApiClient(CONST_BASE_URL, "demo_mode", port = CONST_PORT, verify_ssl = CONST_SSL)
-        responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/objects/products/1", status=400)
+        responses.add(responses.PUT, f"{self.base_url}/objects/products/1", status=400)
         self.assertRaises(HTTPError, api_client.update_product_pic, 1)        
     
     def test_get_expiring_products_valid(self):
         
         expiring_product = self.grocy.expiring_products(True)
 
-        assert isinstance(expiring_product, list)
-        assert len(expiring_product) >= 1
+        self.assertIsInstance(expiring_product, list)
+        self.assertGreaterEqual(len(expiring_product), 1)
         for prod in expiring_product:
-            assert isinstance(prod, Product)
+            self.assertIsInstance(prod, Product)
 
     @responses.activate
     def test_get_expiring_invalid_no_data(self):
@@ -202,23 +242,23 @@ class TestGrocy(TestCase):
             "expired_products": [],
             "missing_products": []
         }
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/stock/volatile", json=resp, status=200)
 
         self.grocy.expiring_products(True)
 
     @responses.activate
     def test_get_expiring_invalid_missing_data(self):
         resp = {}
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/stock/volatile", json=resp, status=200)
         
     def test_get_expired_products_valid(self):
         
         expired_product = self.grocy.expired_products(True)
 
-        assert isinstance(expired_product, list)
-        assert len(expired_product) >= 1
+        self.assertIsInstance(expired_product, list)
+        self.assertGreaterEqual(len(expired_product), 1)
         for prod in expired_product:
-            assert isinstance(prod, Product)
+            self.assertIsInstance(prod, Product)
 
     @responses.activate
     def test_get_expired_invalid_no_data(self):
@@ -227,23 +267,25 @@ class TestGrocy(TestCase):
             "expired_products": [],
             "missing_products": []
         }
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/stock/volatile", json=resp, status=200)
 
         self.grocy.expired_products(True)
 
     @responses.activate
     def test_get_expired_invalid_missing_data(self):
         resp = {}
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/stock/volatile", json=resp, status=200)
         
     def test_get_missing_products_valid(self):
 
         missing_product = self.grocy.missing_products(True)
 
-        assert isinstance(missing_product, list)
-        assert len(missing_product) >= 1
+        self.assertIsInstance(missing_product, list)
+        self.assertGreaterEqual(len(missing_product), 1)
         for prod in missing_product:
-            assert isinstance(prod, Product)
+            self.assertIsInstance(prod, Product)
+            self.assertIsInstance(prod.amount_missing, float)
+            self.assertIsInstance(prod.is_partly_in_stock, bool)
 
     @responses.activate
     def test_get_missing_invalid_no_data(self):
@@ -252,14 +294,14 @@ class TestGrocy(TestCase):
             "expired_products": [],
             "missing_products": []
         }
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/stock/volatile", json=resp, status=200)
 
         self.grocy.missing_products(True)
 
     @responses.activate
     def test_get_missing_invalid_missing_data(self):
         resp = {}
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/stock/volatile", json=resp, status=200)
         
     @responses.activate
     def test_get_userfields_valid(self):
@@ -268,35 +310,65 @@ class TestGrocy(TestCase):
                 "uf2": "string"
             }
         
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/userfields/chores/1", json=resp, status=200)
+        responses.add(responses.GET, f"{self.base_url}/userfields/chores/1", json=resp, status=200)
 
         a_chore_uf = self.grocy.get_userfields("chores",1)
 
-        assert a_chore_uf['uf1'] == 0
+        self.assertEqual(a_chore_uf['uf1'], 0)
 
     def test_get_userfields_invalid_no_data(self):
         self.grocy.get_userfields("chores",1)
 
     @responses.activate
     def test_set_userfields_valid(self):
-        responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/userfields/chores/1", status=204)
+        responses.add(responses.PUT, f"{self.base_url}/userfields/chores/1", status=204)
         self.grocy.set_userfields("chores",1,"auserfield", "value")
         
     @responses.activate
     def test_set_userfields_error(self):
-        responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/userfields/chores/1", status=400)
+        responses.add(responses.PUT, f"{self.base_url}/userfields/chores/1", status=400)
         self.assertRaises(HTTPError, self.grocy.set_userfields, "chores",1,"auserfield","value")
 
     def test_get_last_db_changed_valid(self):
 
         timestamp = self.grocy.get_last_db_changed()
 
-        assert isinstance(timestamp, datetime)
+        self.assertIsInstance(timestamp, datetime)
 
 
     @responses.activate
     def test_get_last_db_changed_invalid_no_data(self):
         resp = {}
-        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/system/db-changed-time", json=resp ,status=200)
+        responses.add(responses.GET, f"{self.base_url}/system/db-changed-time", json=resp ,status=200)
+
+        self.assertIsNone(self.grocy.get_last_db_changed())
+
+    @responses.activate
+    def test_add_product_valid(self):
+        responses.add(responses.POST, f"{self.base_url}/stock/products/1/add", status=200)
+        self.assertIsNone(self.grocy.add_product(1, 1.3, 2.44, self.date_test))
+
+    @responses.activate
+    def test_add_product_error(self):
+        responses.add(responses.POST, f"{self.base_url}/stock/products/1/add", status=400)
+        self.assertRaises(HTTPError, self.grocy.add_product, 1, 1.3, 2.44, self.date_test)
+
+    @responses.activate
+    def test_consume_product_valid(self):
+        responses.add(responses.POST, f"{self.base_url}/stock/products/1/consume", status=200)
+        self.assertIsNone(self.grocy.consume_product(1, 1.3, self.date_test))
+
+    @responses.activate
+    def test_consume_product_error(self):
+        responses.add(responses.POST, f"{self.base_url}/stock/products/1/consume", status=400)
+        self.assertRaises(HTTPError, self.grocy.consume_product, 1, 1.3, self.date_test)
+
+    @responses.activate
+    def test_execute_chore_valid(self):
+        responses.add(responses.POST, f"{self.base_url}/chores/1/execute", status=200)
+        self.assertIsNone(self.grocy.execute_chore(1, 1, self.date_test))
 
-        assert self.grocy.get_last_db_changed() is None
+    @responses.activate
+    def test_execute_chore_error(self):
+        responses.add(responses.POST, f"{self.base_url}/chores/1/execute", status=400)
+        self.assertRaises(HTTPError, self.grocy.execute_chore, 1, 1, self.date_test)