Ejemplo n.º 1
0
class TestGrocy(TestCase):

    def setUp(self):
        self.grocy = Grocy(CONST_BASE_URL, "api_key")
        self.grocy = None
        self.grocy = Grocy(CONST_BASE_URL, "demo_mode",  verify_ssl = CONST_SSL, port = CONST_PORT)

    def test_init(self):
        assert isinstance(self.grocy, Grocy)
        
    def test_get_chores_valid(self):
        chores = self.grocy.chores(get_details=True)
        
        assert isinstance(chores, list)
        assert 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

    def test_product_get_details_valid(self):
        stock = self.grocy.stock()

        product = stock[0]

        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)

    @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

    def test_get_stock_valid(self):
        stock = self.grocy.stock()

        assert isinstance(stock, list)
        assert len(stock) >= 10
        for prod in stock:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_stock_invalid_no_data(self):
        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock", status=200)

        assert self.grocy.stock() is None

    @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)
        
    def test_get_shopping_list_valid(self):
        shopping_list = self.grocy.shopping_list(True)
        
        assert isinstance(shopping_list, list)
        assert len(shopping_list) >= 1
        for item in shopping_list:
            assert isinstance(item, ShoppingListProduct)
            
    @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)
        assert self.grocy.shopping_list() is None
        
    @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)
        
    def test_add_missing_product_to_shopping_list_valid(self):
        assert self.grocy.add_missing_product_to_shopping_list().status_code == 204
        
    @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)
        assert self.grocy.add_missing_product_to_shopping_list().status_code != 204
        
    def test_add_product_to_shopping_list_valid(self):
        assert self.grocy.add_product_to_shopping_list(22).status_code == 204
        
    def test_add_product_to_shopping_list_error(self):
        assert self.grocy.add_product_to_shopping_list(3000).status_code != 204
        
    @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)
        assert self.grocy.clear_shopping_list().status_code == 204
        
    @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)
        assert self.grocy.clear_shopping_list().status_code != 204
        
    @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)
        assert self.grocy.remove_product_in_shopping_list(1).status_code == 204
        
    @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)
        assert self.grocy.remove_product_in_shopping_list(1).status_code != 204
        
    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)
            
    @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)
        assert self.grocy.product_groups() is None
        
    @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.activate
    def test_upload_product_picture_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)
                assert api_client.upload_product_picture(1,"/somepath/pic.jpg").status_code == 204
            
    @responses.activate
    def test_upload_product_picture_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
        
    @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)
                assert api_client.upload_product_picture(1,"/somepath/pic.jpg").status_code != 204
                
    @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)
        assert api_client.update_product_pic(1).status_code == 204
        
    @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)
        assert api_client.update_product_pic(1).status_code != 204
        
    
    def test_get_expiring_products_valid(self):
        
        expiring_product = self.grocy.expiring_products(True)

        assert isinstance(expiring_product, list)
        assert len(expiring_product) >= 1
        for prod in expiring_product:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_expiring_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)

        assert not 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)
        
    def test_get_expired_products_valid(self):
        
        expired_product = self.grocy.expired_products(True)

        assert isinstance(expired_product, list)
        assert len(expired_product) >= 1
        for prod in expired_product:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_expired_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)

        assert not 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)
        
    def test_get_missing_products_valid(self):

        missing_product = self.grocy.missing_products(True)

        assert isinstance(missing_product, list)
        assert len(missing_product) >= 1
        for prod in missing_product:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_missing_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/stock/volatile", json=resp, status=200)

        assert not 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.activate
    def test_get_userfields_valid(self):
        resp =  {
                "uf1": 0,
                "uf2": "string"
            }
        
        responses.add(responses.GET, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/userfields/chores/1", json=resp, status=200)

        a_chore_uf = self.grocy.get_userfields("chores",1)

        assert a_chore_uf['uf1'] == 0

    def test_get_userfields_invalid_no_data(self):
        assert not 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)
        assert self.grocy.set_userfields("chores",1,"auserfield","value").status_code == 204
        
    @responses.activate
    def test_set_userfields_error(self):
        responses.add(responses.PUT, '{}:{}'.format(CONST_BASE_URL,CONST_PORT) + "/api/userfields/chores/1", status=400)
        assert self.grocy.set_userfields("chores",1,"auserfield","value").status_code != 204
        

    def test_get_last_db_changed_valid(self):

        timestamp = self.grocy.get_last_db_changed()

        assert isinstance(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)

        assert self.grocy.get_last_db_changed() is None
Ejemplo n.º 2
0
class TestGrocy(TestCase):
    def setUp(self):
        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):
        self.assertIsInstance(self.grocy, Grocy)

    @unittest.skip("no tasks_current table in current demo data")
    def test_get_tasks_valid(self):
        tasks = self.grocy.tasks()

        assert len(tasks) == 6
        assert tasks[0].id == 1
        assert tasks[0].name == "Repair the garage door"

    @unittest.skip("no chores_current table in current demo data")
    def test_get_chores_valid(self):
        chores = self.grocy.chores(get_details=True)

        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": "who-least-did-first",
                "assignment_config": null,
                "next_execution_assigned_to_user_id": null,
                "consume_product_on_execution": "0",
                "product_id": null,
                "product_amount": null,
                "period_interval": "1"
            },
            "tracked_count": 3,
            "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, Chore)
        self.assertEqual(chore_details.assignment_type,
                         AssignmentType.WHO_LEAST_DID_FIRST)

    @unittest.skip("no stock_current table in current demo data")
    def test_product_get_details_valid(self):
        stock = self.grocy.stock()

        product = stock[0]

        api_client = GrocyApiClient(CONST_BASE_URL,
                                    "demo_mode",
                                    port=CONST_PORT,
                                    verify_ssl=CONST_SSL)
        product.get_details(api_client)

        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):
        responses.add(responses.GET,
                      f"{self.base_url}/stock/products/0",
                      status=200)
        product = self.grocy.product(0)
        self.assertIsNone(product)

    @unittest.skip("no stock_current table in current demo data")
    def test_get_stock_valid(self):
        stock = self.grocy.stock()

        self.assertIsInstance(stock, list)
        self.assertGreaterEqual(len(stock), 10)
        for prod in stock:
            self.assertIsInstance(prod, Product)

    @responses.activate
    def test_get_stock_invalid_missing_data(self):
        resp = []
        responses.add(responses.GET,
                      f"{self.base_url}/stock",
                      json=resp,
                      status=200)
        self.assertEqual(len(self.grocy.stock()), 0)

    @unittest.skip("no userentities table in current demo data")
    def test_get_shopping_list_valid(self):
        shopping_list = self.grocy.shopping_list(True)

        self.assertIsInstance(shopping_list, list)
        self.assertGreaterEqual(len(shopping_list), 1)
        for item in shopping_list:
            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,
                      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,
            f"{self.base_url}/objects/shopping_list",
            json=resp,
            status=200,
        )
        self.assertEqual(len(self.grocy.shopping_list()), 0)

    @unittest.skip("no shopping list existing in current demo data")
    def test_add_missing_product_to_shopping_list_valid(self):
        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,
            f"{self.base_url}/stock/shoppinglist/add-missing-products",
            status=400,
        )
        self.assertRaises(HTTPError,
                          self.grocy.add_missing_product_to_shopping_list)

    @unittest.skip("no shopping list existing in current demo data")
    def test_add_product_to_shopping_list_valid(self):
        self.grocy.add_product_to_shopping_list(3)

    @unittest.skip("no shopping list existing in current demo data")
    def test_add_product_to_shopping_list_error(self):
        self.assertRaises(HTTPError, self.grocy.add_product_to_shopping_list,
                          3000)

    @responses.activate
    def test_clear_shopping_list_valid(self):
        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,
                      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,
            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,
            f"{self.base_url}/stock/shoppinglist/remove-product",
            status=400,
        )
        self.assertRaises(HTTPError,
                          self.grocy.remove_product_in_shopping_list, 1)

    @unittest.skip("no userentities table in current demo data")
    def test_get_product_groups_valid(self):
        product_groups_list = self.grocy.product_groups()

        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,
                      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,
            f"{self.base_url}/objects/product_groups",
            json=resp,
            status=200,
        )
        self.assertEqual(len(self.grocy.product_groups()), 0)

    @responses.activate
    def test_add_product_pic_valid(self):
        with patch("os.path.exists") as m_exist:
            with patch("builtins.open", mock_open()):
                m_exist.return_value = True
                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_add_product_pic_invalid_missing_data(self):
        with patch("os.path.exists") as m_exist:
            m_exist.return_value = False
            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()):
                m_exist.return_value = True
                api_client = GrocyApiClient(CONST_BASE_URL,
                                            "demo_mode",
                                            port=CONST_PORT,
                                            verify_ssl=CONST_SSL)
                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_error(self):
        api_client = GrocyApiClient(CONST_BASE_URL,
                                    "demo_mode",
                                    port=CONST_PORT,
                                    verify_ssl=CONST_SSL)
        responses.add(responses.PUT,
                      f"{self.base_url}/objects/products/1",
                      status=400)
        self.assertRaises(HTTPError, api_client.update_product_pic, 1)

    @unittest.skip("no stock_current table in current demo data")
    def test_get_expiring_products_valid(self):

        expiring_product = self.grocy.expiring_products(True)

        self.assertIsInstance(expiring_product, list)
        self.assertGreaterEqual(len(expiring_product), 1)
        for prod in expiring_product:
            self.assertIsInstance(prod, Product)

    @responses.activate
    def test_get_expiring_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        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,
                      f"{self.base_url}/stock/volatile",
                      json=resp,
                      status=200)

    @unittest.skip("no stock_current table in current demo data")
    def test_get_expired_products_valid(self):

        expired_product = self.grocy.expired_products(True)

        self.assertIsInstance(expired_product, list)
        self.assertGreaterEqual(len(expired_product), 1)
        for prod in expired_product:
            self.assertIsInstance(prod, Product)

    @responses.activate
    def test_get_expired_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        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,
                      f"{self.base_url}/stock/volatile",
                      json=resp,
                      status=200)

    @unittest.skip("no stock_current table in current demo data")
    def test_get_missing_products_valid(self):

        missing_product = self.grocy.missing_products(True)

        self.assertIsInstance(missing_product, list)
        self.assertGreaterEqual(len(missing_product), 1)
        for prod in missing_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):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        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,
                      f"{self.base_url}/stock/volatile",
                      json=resp,
                      status=200)

    @responses.activate
    def test_get_userfields_valid(self):
        resp = {"uf1": 0, "uf2": "string"}

        responses.add(responses.GET,
                      f"{self.base_url}/userfields/chores/1",
                      json=resp,
                      status=200)

        a_chore_uf = self.grocy.get_userfields("chores", 1)

        self.assertEqual(a_chore_uf["uf1"], 0)

    @unittest.skip("no userentities table in current demo data")
    def test_get_userfields_invalid_no_data(self):
        self.assertRaises(HTTPError, self.grocy.get_userfields("chores", 1))

    @responses.activate
    def test_set_userfields_valid(self):
        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,
                      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()

        self.assertIsInstance(timestamp, datetime)

    @responses.activate
    def test_get_last_db_changed_invalid_no_data(self):
        resp = {}
        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))

    @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)

    @responses.activate
    def test_get_meal_plan(self):
        resp_json = json.loads("""[
              {
                "id": "1",
                "day": "2020-08-10",
                "type": "recipe",
                "recipe_id": "1",
                "recipe_servings": "1",
                "note": null,
                "product_id": null,
                "product_amount": "0.0",
                "product_qu_id": null,
                "row_created_timestamp": "2020-08-12 19:59:30",
                "userfields": null
              }
          ]""")
        responses.add(
            responses.GET,
            f"{self.base_url}/objects/meal_plan",
            json=resp_json,
            status=200,
        )
        meal_plan = self.grocy.meal_plan()
        self.assertEqual(len(meal_plan), 1)
        self.assertEqual(meal_plan[0].id, 1)
        self.assertEqual(meal_plan[0].recipe_id, 1)

    @responses.activate
    def test_get_recipe(self):
        resp_json = json.loads("""{
          "id": "1",
          "name": "Pizza",
          "description": "<p>Mix everything</p>",
          "row_created_timestamp": "2020-08-12 11:37:34",
          "picture_file_name": "51si0q0wsiq5imo4f8wbIMG_5709.jpeg",
          "base_servings": "4",
          "desired_servings": "4",
          "not_check_shoppinglist": "0",
          "type": "normal",
          "product_id": "",
          "userfields": null
        }""")
        responses.add(
            responses.GET,
            f"{self.base_url}/objects/recipes/1",
            json=resp_json,
            status=200,
        )
        recipe = self.grocy.recipe(1)
        self.assertEqual(recipe.id, 1)
        self.assertEqual(recipe.name, "Pizza")
        self.assertEqual(recipe.base_servings, 4)
        self.assertIsInstance(recipe.get_picture_url_path(400), str)
Ejemplo n.º 3
0
class TestGrocy(TestCase):
    def setUp(self):
        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")
        self.add_generic_data = {"name": "This is a task"}

    @responses.activate
    def test_get_shopping_list_invalid_no_data(self):
        responses.add(responses.GET,
                      f"{self.base_url}/objects/shopping_list",
                      status=400)
        self.assertRaises(GrocyError, self.grocy.shopping_list)

    @responses.activate
    def test_get_shopping_list_invalid_missing_data(self):
        resp = []
        responses.add(
            responses.GET,
            f"{self.base_url}/objects/shopping_list",
            json=resp,
            status=200,
        )
        self.assertEqual(len(self.grocy.shopping_list()), 0)

    @responses.activate
    def test_clear_shopping_list_error(self):
        responses.add(responses.POST,
                      f"{self.base_url}/stock/shoppinglist/clear",
                      status=400)
        self.assertRaises(GrocyError, self.grocy.clear_shopping_list)

    @responses.activate
    def test_remove_product_in_shopping_list_error(self):
        responses.add(
            responses.POST,
            f"{self.base_url}/stock/shoppinglist/remove-product",
            status=400,
        )
        self.assertRaises(GrocyError,
                          self.grocy.remove_product_in_shopping_list, 1)

    @responses.activate
    def test_get_product_groups_invalid_no_data(self):
        responses.add(responses.GET,
                      f"{self.base_url}/objects/product_groups",
                      status=400)
        self.assertRaises(GrocyError, self.grocy.product_groups)

    @responses.activate
    def test_get_product_groups_invalid_missing_data(self):
        resp = []
        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_add_product_pic_invalid_missing_data(self):
        with patch("os.path.exists") as m_exist:
            m_exist.return_value = False
            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()):
                m_exist.return_value = True
                api_client = GrocyApiClient(CONST_BASE_URL,
                                            "demo_mode",
                                            port=CONST_PORT,
                                            verify_ssl=CONST_SSL)
                responses.add(
                    responses.PUT,
                    f"{self.base_url}/files/productpictures/MS5qcGc=",
                    status=400,
                )
                self.assertRaises(
                    GrocyError,
                    api_client.upload_product_picture,
                    1,
                    "/somepath/pic.jpg",
                )

    @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,
                      f"{self.base_url}/objects/products/1",
                      status=400)
        self.assertRaises(GrocyError, api_client.update_product_pic, 1)

    @responses.activate
    def test_get_due_invalid_no_data(self):
        resp = {
            "due_products": [],
            "expired_products": [],
            "missing_products": []
        }
        responses.add(responses.GET,
                      f"{self.base_url}/stock/volatile",
                      json=resp,
                      status=200)

        self.grocy.due_products(True)

    @responses.activate
    def test_get_expired_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        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,
                      f"{self.base_url}/stock/volatile",
                      json=resp,
                      status=200)

    @responses.activate
    def test_get_missing_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        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,
                      f"{self.base_url}/stock/volatile",
                      json=resp,
                      status=200)

    @responses.activate
    def test_get_userfields_valid(self):
        resp = {"uf1": 0, "uf2": "string"}

        responses.add(responses.GET,
                      f"{self.base_url}/userfields/chores/1",
                      json=resp,
                      status=200)

        a_chore_uf = self.grocy.get_userfields("chores", 1)

        self.assertEqual(a_chore_uf["uf1"], 0)

    @responses.activate
    def test_set_userfields_valid(self):
        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,
                      f"{self.base_url}/userfields/chores/1",
                      status=400)
        self.assertRaises(GrocyError, self.grocy.set_userfields, "chores", 1,
                          "auserfield", "value")

    @responses.activate
    def test_get_last_db_changed_invalid_no_data(self):
        resp = {}
        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(GrocyError, 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))

    @responses.activate
    def test_consume_product_error(self):
        responses.add(responses.POST,
                      f"{self.base_url}/stock/products/1/consume",
                      status=400)
        self.assertRaises(GrocyError, self.grocy.consume_product, 1, 1.3)

    @pytest.mark.vcr
    def test_inventory_product_valid(self):
        current_inventory = int(self.grocy.product(4).available_amount)
        new_amount = current_inventory + 10

        product = self.grocy.inventory_product(4, new_amount, self.date_test,
                                               1, 1, 150, True)

        assert product.id == 4
        assert product.name == "Crisps"
        assert product.available_amount == new_amount

    @pytest.mark.vcr
    def test_inventory_product_error(self):
        with pytest.raises(GrocyError) as exc_info:
            current_inventory = int(self.grocy.product(4).available_amount)
            self.grocy.inventory_product(4, current_inventory, self.date_test,
                                         1, 1, 150, True)

        error = exc_info.value
        assert error.status_code == 400

    @pytest.mark.vcr
    def test_add_product_by_barcode_valid(self):
        product = self.grocy.add_product_by_barcode("42141099", 1, 5,
                                                    self.date_test, True)

        assert isinstance(product, Product)
        assert product.id == 4
        assert product.name == "Crisps"

    @pytest.mark.vcr
    def test_add_product_by_barcode_error(self):
        with pytest.raises(GrocyError) as exc_info:
            self.grocy.add_product_by_barcode("555", 1, 5, self.date_test,
                                              True)

        error = exc_info.value
        assert error.status_code == 400

    @pytest.mark.vcr
    def test_consume_product_by_barcode_valid(self):
        product = self.grocy.consume_product_by_barcode(
            "42141099", 1, False, True)

        assert isinstance(product, Product)
        assert product.id == 4
        assert product.name == "Crisps"

    @pytest.mark.vcr
    def test_consume_product_by_barcode_error(self):
        with pytest.raises(GrocyError) as exc_info:
            self.grocy.consume_product_by_barcode("555", 1, False, True)

        error = exc_info.value
        assert error.status_code == 400

    @pytest.mark.vcr
    def test_inventory_product_by_barcode_valid(self):
        currentInv = int(
            self.grocy.product_by_barcode("42141099").available_amount)
        newAmount = currentInv + 10

        product = self.grocy.inventory_product_by_barcode(
            "42141099", newAmount, self.date_test, 1, 150, True)

        assert product.id == 4
        assert product.name == "Crisps"
        assert product.available_amount == newAmount

    @pytest.mark.vcr
    def test_inventory_product_by_barcode_error(self):
        with pytest.raises(GrocyError) as exc_info:
            currentInv = int(
                self.grocy.product_by_barcode("42141099").available_amount)
            self.grocy.inventory_product(4, currentInv, self.date_test, 1, 150,
                                         True)

        error = exc_info.value
        assert error.status_code == 400

    @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))

    @responses.activate
    def test_execute_chore_error(self):
        responses.add(responses.POST,
                      f"{self.base_url}/chores/1/execute",
                      status=400)
        self.assertRaises(GrocyError, self.grocy.execute_chore, 1, 1,
                          self.date_test)

    @responses.activate
    def test_get_meal_plan(self):
        resp_json = json.loads("""[
              {
                "id": "1",
                "day": "2020-08-10",
                "type": "recipe",
                "recipe_id": "1",
                "recipe_servings": "1",
                "note": null,
                "product_id": null,
                "product_amount": "0.0",
                "product_qu_id": null,
                "row_created_timestamp": "2020-08-12 19:59:30",
                "userfields": null
              }
          ]""")
        responses.add(
            responses.GET,
            f"{self.base_url}/objects/meal_plan",
            json=resp_json,
            status=200,
        )
        meal_plan = self.grocy.meal_plan()
        self.assertEqual(len(meal_plan), 1)
        self.assertEqual(meal_plan[0].id, 1)
        self.assertEqual(meal_plan[0].recipe_id, 1)

    @responses.activate
    def test_get_recipe(self):
        resp_json = json.loads("""{
          "id": "1",
          "name": "Pizza",
          "description": "<p>Mix everything</p>",
          "row_created_timestamp": "2020-08-12 11:37:34",
          "picture_file_name": "51si0q0wsiq5imo4f8wbIMG_5709.jpeg",
          "base_servings": "4",
          "desired_servings": "4",
          "not_check_shoppinglist": "0",
          "type": "normal",
          "product_id": "",
          "userfields": null
        }""")
        responses.add(
            responses.GET,
            f"{self.base_url}/objects/recipes/1",
            json=resp_json,
            status=200,
        )
        recipe = self.grocy.recipe(1)
        self.assertEqual(recipe.id, 1)
        self.assertEqual(recipe.name, "Pizza")
        self.assertEqual(recipe.base_servings, 4)
        self.assertIsInstance(recipe.get_picture_url_path(400), str)
Ejemplo n.º 4
0
class TestGrocy(TestCase):
    def setUp(self):
        self.grocy = Grocy("https://example.com", "api_key")

    def test_init(self):
        assert isinstance(self.grocy, Grocy)

    @responses.activate
    def test_get_chores_valid_no_details(self):
        resp = [{
            "chore_id": "1",
            "last_tracked_time": "2019-11-18 00:00:00",
            "next_estimated_execution_time": "2019-11-25 00:00:00",
            "track_date_only": "1"
        }, {
            "chore_id": "2",
            "last_tracked_time": "2019-11-16 00:00:00",
            "next_estimated_execution_time": "2019-11-23 00:00:00",
            "track_date_only": "1"
        }, {
            "chore_id": "3",
            "last_tracked_time": "2019-11-10 00:00:00",
            "next_estimated_execution_time": "2019-12-10 00:00:00",
            "track_date_only": "1"
        }, {
            "chore_id": "4",
            "last_tracked_time": "2019-11-18 00:00:00",
            "next_estimated_execution_time": "2019-11-25 00:00:00",
            "track_date_only": "1",
        }]

        responses.add(responses.GET,
                      "https://example.com:9192/api/chores",
                      json=resp,
                      status=200)
        chores = self.grocy.chores(get_details=False)

        assert isinstance(chores, list)
        assert len(chores) == 4
        assert chores[0].chore_id == 1
        assert chores[1].chore_id == 2
        assert chores[2].chore_id == 3
        assert chores[3].chore_id == 4

    @responses.activate
    def test_product_get_details_valid(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("https://example.com", "api_key")

        resp = {
            "product": {
                "id": 0,
                "name": "string",
                "description": "string",
                "location_id": 0,
                "qu_id_purchase": 0,
                "qu_id_stock": 0,
                "qu_factor_purchase_to_stock": 0,
                "barcode": "string",
                "product_group_id": 0,
                "min_stock_amount": 0,
                "default_best_before_days": 0,
                "picture_file_name": "string",
                "allow_partial_units_in_stock": True,
                "row_created_timestamp": "2019-05-02T18:30:48.041Z"
            },
            "quantity_unit_purchase": {
                "id": 0,
                "name": "string",
                "name_plural": "string",
                "description": "string",
                "row_created_timestamp": "2019-05-02T18:30:48.041Z"
            },
            "quantity_unit_stock": {
                "id": 0,
                "name": "string",
                "name_plural": "string",
                "description": "string",
                "row_created_timestamp": "2019-05-02T18:30:48.041Z"
            },
            "last_purchased": "2019-05-02",
            "last_used": "2019-05-02T18:30:48.041Z",
            "stock_amount": 0,
            "stock_amount_opened": 0,
            "next_best_before_date": "2019-05-02T18:30:48.041Z",
            "last_price": 0,
            "location": {
                "id": 0,
                "name": "string",
                "description": "string",
                "row_created_timestamp": "2019-05-02T18:30:48.041Z"
            }
        }
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/products/0",
                      json=resp,
                      status=200)

        product.get_details(api_client)

        assert product.name == "string"
        assert product.product_group_id == 0

    @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("https://example.com", "api_key")

        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/products/0",
                      status=200)

        product.get_details(api_client)

        assert product.name is None

    @responses.activate
    def test_get_stock_valid(self):
        resp = [{
            "product_id": 0,
            "amount": "0.33",
            "best_before_date": "2019-05-02"
        }]
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock",
                      json=resp,
                      status=200)

        stock = self.grocy.stock()

        assert isinstance(stock, list)
        assert len(stock) == 1
        for prod in stock:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_stock_invalid_no_data(self):
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock",
                      status=200)

        assert self.grocy.stock() is None

    @responses.activate
    def test_get_stock_invalid_missing_data(self):
        resp = [{}]
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock",
                      json=resp,
                      status=200)

    @responses.activate
    def test_get_shopping_list_valid(self):
        resp = [{
            "id": 1,
            "product_id": 6,
            "note": "string",
            "amount": 2,
            "row_created_timestamp": "2019-04-17 10:30:00",
            "shopping_list_id": 1,
            "done": 0
        }]
        responses.add(responses.GET,
                      "https://example.com:9192/api/objects/shopping_list",
                      json=resp,
                      status=200)

        shopping_list = self.grocy.shopping_list()

        assert isinstance(shopping_list, list)
        assert len(shopping_list) == 1
        for item in shopping_list:
            assert isinstance(item, ShoppingListProduct)

    @responses.activate
    def test_get_shopping_list_invalid_no_data(self):
        responses.add(responses.GET,
                      "https://example.com:9192/api/objects/shopping_list",
                      status=400)
        assert self.grocy.shopping_list() is None

    @responses.activate
    def test_get_shopping_list_invalid_missing_data(self):
        resp = [{}]
        responses.add(responses.GET,
                      "https://example.com:9192/api/objects/shopping_list",
                      json=resp,
                      status=200)

    @responses.activate
    def test_add_missing_product_to_shopping_list_valid(self):
        responses.add(
            responses.POST,
            "https://example.com:9192/api/stock/shoppinglist/add-missing-products",
            status=204)
        assert self.grocy.add_missing_product_to_shopping_list(
        ).status_code == 204

    @responses.activate
    def test_add_missing_product_to_shopping_list_error(self):
        responses.add(
            responses.POST,
            "https://example.com:9192/api/stock/shoppinglist/add-missing-products",
            status=400)
        assert self.grocy.add_missing_product_to_shopping_list(
        ).status_code != 204

    @responses.activate
    def test_add_product_to_shopping_list_valid(self):
        responses.add(
            responses.POST,
            "https://example.com:9192/api/stock/shoppinglist/add-product",
            status=204)
        assert self.grocy.add_product_to_shopping_list(1).status_code == 204

    @responses.activate
    def test_add_product_to_shopping_list_error(self):
        responses.add(
            responses.POST,
            "https://example.com:9192/api/stock/shoppinglist/add-product",
            status=400)
        assert self.grocy.add_product_to_shopping_list(1).status_code != 204

    @responses.activate
    def test_clear_shopping_list_valid(self):
        responses.add(responses.POST,
                      "https://example.com:9192/api/stock/shoppinglist/clear",
                      status=204)
        assert self.grocy.clear_shopping_list().status_code == 204

    @responses.activate
    def test_clear_shopping_list_error(self):
        responses.add(responses.POST,
                      "https://example.com:9192/api/stock/shoppinglist/clear",
                      status=400)
        assert self.grocy.clear_shopping_list().status_code != 204

    @responses.activate
    def test_remove_product_in_shopping_list_valid(self):
        responses.add(
            responses.POST,
            "https://example.com:9192/api/stock/shoppinglist/remove-product",
            status=204)
        assert self.grocy.remove_product_in_shopping_list(1).status_code == 204

    @responses.activate
    def test_remove_product_in_shopping_list_error(self):
        responses.add(
            responses.POST,
            "https://example.com:9192/api/stock/shoppinglist/remove-product",
            status=400)
        assert self.grocy.remove_product_in_shopping_list(1).status_code != 204

    @responses.activate
    def test_get_product_groups_valid(self):
        resp = [{
            "id": 1,
            "name": "string",
            "description": "string",
            "row_created_timestamp": "2019-04-17 10:30:00",
        }]
        responses.add(responses.GET,
                      "https://example.com:9192/api/objects/product_groups",
                      json=resp,
                      status=200)
        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)

    @responses.activate
    def test_get_product_groups_invalid_no_data(self):
        responses.add(responses.GET,
                      "https://example.com:9192/api/objects/product_groups",
                      status=400)
        assert self.grocy.product_groups() is None

    @responses.activate
    def test_get_product_groups_invalid_missing_data(self):
        resp = [{}]
        responses.add(responses.GET,
                      "https://example.com:9192/api/objects/product_groups",
                      json=resp,
                      status=200)

    @responses.activate
    def test_upload_product_picture_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("https://example.com", "api_key")
                responses.add(
                    responses.PUT,
                    "https://example.com:9192/api/files/productpictures/MS5qcGc=",
                    status=204)
                assert api_client.upload_product_picture(
                    1, "/somepath/pic.jpg").status_code == 204

    @responses.activate
    def test_upload_product_picture_invalid_missing_data(self):
        with patch("os.path.exists") as m_exist:
            m_exist.return_value = False
            api_client = GrocyApiClient("https://example.com", "api_key")
            responses.add(
                responses.PUT,
                "https://example.com:9192/api/files/productpictures/MS5qcGc=",
                status=204)
            assert api_client.upload_product_picture(
                1, "/somepath/pic.jpg") is None

    @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("https://example.com", "api_key")
                responses.add(
                    responses.PUT,
                    "https://example.com:9192/api/files/productpictures/MS5qcGc=",
                    status=400)
                assert api_client.upload_product_picture(
                    1, "/somepath/pic.jpg").status_code != 204

    @responses.activate
    def test_update_product_pic_valid(self):
        api_client = GrocyApiClient("https://example.com", "api_key")
        responses.add(responses.PUT,
                      "https://example.com:9192/api/objects/products/1",
                      status=204)
        assert api_client.update_product_pic(1).status_code == 204

    @responses.activate
    def test_update_product_pic_error(self):
        api_client = GrocyApiClient("https://example.com", "api_key")
        responses.add(responses.PUT,
                      "https://example.com:9192/api/objects/products/1",
                      status=400)
        assert api_client.update_product_pic(1).status_code != 204

    @responses.activate
    def test_get_expiring_products_valid(self):
        resp = {
            "expiring_products": [{
                "product_id": 0,
                "amount": "0.33",
                "best_before_date": "2019-05-02",
                "amount_opened": "0"
            }],
            "expired_products": [],
            "missing_products": []
        }

        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

        expiring_product = self.grocy.expiring_products()

        assert isinstance(expiring_product, list)
        assert len(expiring_product) == 1
        for prod in expiring_product:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_expiring_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

        assert not self.grocy.expiring_products()

    @responses.activate
    def test_get_expiring_invalid_missing_data(self):
        resp = {}
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

    @responses.activate
    def test_get_expired_products_valid(self):
        resp = {
            "expired_products": [{
                "product_id": 0,
                "amount": "0.33",
                "best_before_date": "2019-05-02",
                "amount_opened": "0"
            }],
            "expiring_products": [],
            "missing_products": []
        }

        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

        expired_product = self.grocy.expired_products()

        assert isinstance(expired_product, list)
        assert len(expired_product) == 1
        for prod in expired_product:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_expired_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

        assert not self.grocy.expired_products()

    @responses.activate
    def test_get_expired_invalid_missing_data(self):
        resp = {}
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

    @responses.activate
    def test_get_missing_products_valid(self):
        resp = {
            "missing_products": [{
                "product_id": 0,
                "amount": "0.33",
                "best_before_date": "2019-05-02",
                "amount_opened": "0"
            }],
            "expired_products": [],
            "expiring_products": []
        }

        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

        missing_product = self.grocy.missing_products()

        assert isinstance(missing_product, list)
        assert len(missing_product) == 1
        for prod in missing_product:
            assert isinstance(prod, Product)

    @responses.activate
    def test_get_missing_invalid_no_data(self):
        resp = {
            "expiring_products": [],
            "expired_products": [],
            "missing_products": []
        }
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

        assert not self.grocy.missing_products()

    @responses.activate
    def test_get_stock_invalid_missing_data(self):
        resp = {}
        responses.add(responses.GET,
                      "https://example.com:9192/api/stock/volatile",
                      json=resp,
                      status=200)

    @responses.activate
    def test_get_userfields_valid(self):
        resp = {"uf1": 0, "uf2": "string"}

        responses.add(responses.GET,
                      "https://example.com:9192/api/userfields/chores/1",
                      json=resp,
                      status=200)

        a_chore_uf = self.grocy.get_userfields("chores", 1)

        assert a_chore_uf['uf1'] == 0

    @responses.activate
    def test_get_userfields_invalid_no_data(self):
        resp = []
        responses.add(responses.GET,
                      "https://example.com:9192/api/userfields/chores/1",
                      json=resp,
                      status=200)

        assert not self.grocy.get_userfields("chores", 1)

    @responses.activate
    def test_set_userfields_valid(self):
        responses.add(responses.PUT,
                      "https://example.com:9192/api/userfields/chores/1",
                      status=204)
        assert self.grocy.set_userfields("chores", 1, "auserfield",
                                         "value").status_code == 204

    @responses.activate
    def test_set_userfields_error(self):
        responses.add(responses.PUT,
                      "https://example.com:9192/api/userfields/chores/1",
                      status=400)
        assert self.grocy.set_userfields("chores", 1, "auserfield",
                                         "value").status_code != 204

    @responses.activate
    def test_get_last_db_changed_valid(self):
        resp = {"changed_time": "2019-09-18T05:30:58.598Z"}

        responses.add(responses.GET,
                      "https://example.com:9192/api/system/db-changed-time",
                      json=resp,
                      status=200)

        timestamp = self.grocy.get_last_db_changed()

        assert isinstance(timestamp, datetime)

    @responses.activate
    def test_get_last_db_changed_invalid_no_data(self):
        resp = {}
        responses.add(responses.GET,
                      "https://example.com:9192/api/system/db-changed-time",
                      json=resp,
                      status=200)

        assert self.grocy.get_last_db_changed() is None