diff --git a/.travis.yml b/.travis.yml index fe88549..72f7fd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,9 @@ addons: sources: - sourceline: 'ppa:deadsnakes/ppa' packages: - - python3.6 - python3.8 - python3.9 + - python3.10 - python3-pip - python3-setuptools - python3-wheel diff --git a/CHANGELOG.md b/CHANGELOG.md index 01953ab..8bbcb49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## [v1.1.0](https://github.com/SebRut/pygrocy/tree/v1.1.0) (2022-03-05) + +[Full Changelog](https://github.com/SebRut/pygrocy/compare/v1.0.0...v1.1.0) + +**Implemented enhancements:** + +- Feature: Edit Stock [\#201](https://github.com/SebRut/pygrocy/issues/201) +- Add path in request when creating base url [\#121](https://github.com/SebRut/pygrocy/issues/121) +- Get product by Barcode [\#85](https://github.com/SebRut/pygrocy/issues/85) + +**Fixed bugs:** + +- min\_stock\_amount should be float, not int [\#217](https://github.com/SebRut/pygrocy/issues/217) + +**Closed issues:** + +- execute\_chore is formatting time in UTC time. grocy server ignores the timezone, so it shifts the date of chore execution [\#222](https://github.com/SebRut/pygrocy/issues/222) +- Update Product Userfields [\#196](https://github.com/SebRut/pygrocy/issues/196) + +**Merged pull requests:** + +- update python [\#219](https://github.com/SebRut/pygrocy/pull/219) ([SebRut](https://github.com/SebRut)) +- Optional float [\#218](https://github.com/SebRut/pygrocy/pull/218) ([harshi1122](https://github.com/harshi1122)) +- Update responses requirement from ~=0.14.0 to ~=0.18.0 [\#216](https://github.com/SebRut/pygrocy/pull/216) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Added support for product quantities [\#214](https://github.com/SebRut/pygrocy/pull/214) ([andreheuer](https://github.com/andreheuer)) +- Update pydantic requirement from ~=1.8.2 to \>=1.8.2,\<1.10.0 [\#212](https://github.com/SebRut/pygrocy/pull/212) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Feature/edit stock [\#209](https://github.com/SebRut/pygrocy/pull/209) ([georgegebbett](https://github.com/georgegebbett)) +- Product by barcode [\#208](https://github.com/SebRut/pygrocy/pull/208) ([georgegebbett](https://github.com/georgegebbett)) +- Update iso8601 requirement from ~=0.1.16 to \>=0.1.16,\<1.1.0 [\#202](https://github.com/SebRut/pygrocy/pull/202) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Update tzlocal requirement from \<3.0,\>=2.1 to \>=2.1,\<5.0 [\#198](https://github.com/SebRut/pygrocy/pull/198) ([dependabot[bot]](https://github.com/apps/dependabot)) +- ProductDetailsResponse last\_used Type Date instead of Datetime [\#194](https://github.com/SebRut/pygrocy/pull/194) ([sebiecker](https://github.com/sebiecker)) +- support path for url [\#193](https://github.com/SebRut/pygrocy/pull/193) ([SebRut](https://github.com/SebRut)) +- Update responses requirement from ~=0.13.4 to ~=0.14.0 [\#191](https://github.com/SebRut/pygrocy/pull/191) ([dependabot[bot]](https://github.com/apps/dependabot)) + ## [v1.0.0](https://github.com/SebRut/pygrocy/tree/v1.0.0) (2021-09-10) [Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.30.0...v1.0.0) @@ -7,7 +41,15 @@ **Implemented enhancements:** - Add assigned\_to\_user to task [\#188](https://github.com/SebRut/pygrocy/issues/188) +- Done time should be optional, as it is for execute\_chore call [\#176](https://github.com/SebRut/pygrocy/issues/176) - Add support for meal plan sections in grocy v3.1.0 [\#172](https://github.com/SebRut/pygrocy/issues/172) +- Add debug mode [\#167](https://github.com/SebRut/pygrocy/issues/167) +- Missing generic put and get option [\#155](https://github.com/SebRut/pygrocy/issues/155) +- add "get all products" method [\#97](https://github.com/SebRut/pygrocy/issues/97) + +**Fixed bugs:** + +- Sending no time when tracking chore gives an error [\#175](https://github.com/SebRut/pygrocy/issues/175) **Closed issues:** @@ -27,14 +69,8 @@ [Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.29.0...v0.30.0) -**Implemented enhancements:** - -- Done time should be optional, as it is for execute\_chore call [\#176](https://github.com/SebRut/pygrocy/issues/176) -- Add debug mode [\#167](https://github.com/SebRut/pygrocy/issues/167) - **Fixed bugs:** -- Sending no time when tracking chore gives an error [\#175](https://github.com/SebRut/pygrocy/issues/175) - Lovelace not accessible [\#158](https://github.com/SebRut/pygrocy/issues/158) **Closed issues:** @@ -63,11 +99,6 @@ [Full Changelog](https://github.com/SebRut/pygrocy/compare/v0.28.0...v0.29.0) -**Implemented enhancements:** - -- Missing generic put and get option [\#155](https://github.com/SebRut/pygrocy/issues/155) -- add "get all products" method [\#97](https://github.com/SebRut/pygrocy/issues/97) - **Closed issues:** - 404 errors and unresponsive sensors after configuring integration in HA [\#154](https://github.com/SebRut/pygrocy/issues/154) diff --git a/README.md b/README.md index e270dab..b61881e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # pygrocy [![Development Build Status](https://api.travis-ci.com/SebRut/pygrocy.svg?branch=develop)](https://travis-ci.com/SebRut/pygrocy) [![PyPI](https://img.shields.io/pypi/v/pygrocy.svg)](https://pypi.org/project/pygrocy/) -![Python Version](https://img.shields.io/badge/python-3.6%20%7C%203.8%20%7C%203.9-blue) +![Python Version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10-blue) ![Grocy Version](https://img.shields.io/badge/grocy-3.1.0-yellow) [![Coverage Status](https://coveralls.io/repos/github/SebRut/pygrocy/badge.svg?branch=master)](https://coveralls.io/github/SebRut/pygrocy?branch=master) [![CodeFactor](https://www.codefactor.io/repository/github/sebrut/pygrocy/badge)](https://www.codefactor.io/repository/github/sebrut/pygrocy) diff --git a/pygrocy/data_models/product.py b/pygrocy/data_models/product.py index 8f6ede0..661155c 100644 --- a/pygrocy/data_models/product.py +++ b/pygrocy/data_models/product.py @@ -10,7 +10,9 @@ ProductBarcodeData, ProductData, ProductDetailsResponse, + QuantityUnitData, ShoppingListItem, + StockLogResponse, ) @@ -23,6 +25,30 @@ def barcode(self) -> str: return self._barcode +class QuantityUnit(DataModel): + def __init__(self, data: QuantityUnitData): + self._id = data.id + self._name = data.name + self._name_plural = data.name_plural + self._description = data.description + + @property + def id(self) -> int: + return self._id + + @property + def name(self) -> str: + return self._name + + @property + def name_plural(self) -> str: + return self._name_plural + + @property + def description(self) -> str: + return self._description + + class Product(DataModel): def __init__(self, data): self._init_empty() @@ -34,6 +60,8 @@ def __init__(self, data): self._init_from_ProductDetailsResponse(data) elif isinstance(data, ProductData): self._init_from_ProductData(data) + elif isinstance(data, StockLogResponse): + self._init_from_StockLogResponse(data) def _init_empty(self): self._name = None @@ -44,6 +72,9 @@ def _init_empty(self): self._available_amount = None self._best_before_date = None + self._default_quantity_unit_purchase = None + self._qu_factor_purchase_to_stock = None + self._barcodes = [] self._product_group_id = None @@ -65,6 +96,9 @@ def _init_from_ProductDetailsResponse(self, response: ProductDetailsResponse): self._available_amount = response.stock_amount self._best_before_date = response.next_best_before_date self._barcodes = [ProductBarcode(data) for data in response.barcodes] + self._default_quantity_unit_purchase = QuantityUnit( + response.default_quantity_unit_purchase + ) if response.product: self._init_from_ProductData(response.product) @@ -73,6 +107,10 @@ def _init_from_ProductData(self, product: ProductData): self._id = product.id self._product_group_id = product.product_group_id self._name = product.name + self._qu_factor_purchase_to_stock = product.qu_factor_purchase_to_stock + + def _init_from_StockLogResponse(self, response: StockLogResponse): + self._id = response.product_id def get_details(self, api_client: GrocyApiClient): details = api_client.get_product(self.id) @@ -80,6 +118,7 @@ def get_details(self, api_client: GrocyApiClient): self._name = details.product.name self._barcodes = [ProductBarcode(barcode) for barcode in details.barcodes] self._product_group_id = details.product.product_group_id + self._available_amount = details.stock_amount @property def name(self) -> str: @@ -117,6 +156,14 @@ def amount_missing(self) -> float: def is_partly_in_stock(self) -> int: return self._is_partly_in_stock + @property + def default_quantity_unit_purchase(self) -> QuantityUnit: + return self._default_quantity_unit_purchase + + @property + def qu_factor_purchase_to_stock(self) -> float: + return self._qu_factor_purchase_to_stock + class Group(DataModel): def __init__(self, raw_product_group: LocationData): diff --git a/pygrocy/grocy.py b/pygrocy/grocy.py index ed5a5f9..e2af24e 100644 --- a/pygrocy/grocy.py +++ b/pygrocy/grocy.py @@ -36,10 +36,13 @@ def __init__( base_url, api_key, port: int = DEFAULT_PORT_NUMBER, + path: str = None, verify_ssl=True, debug=False, ): - self._api_client = GrocyApiClient(base_url, api_key, port, verify_ssl, debug) + self._api_client = GrocyApiClient( + base_url, api_key, port, path, verify_ssl, debug + ) if debug: _LOGGER.setLevel(logging.DEBUG) @@ -94,6 +97,11 @@ def product(self, product_id: int) -> Product: if resp: return Product(resp) + def product_by_barcode(self, barcode: str) -> Product: + resp = self._api_client.get_product_by_barcode(barcode) + if resp: + return Product(resp) + def all_products(self) -> List[Product]: raw_products = self.get_generic_objects_for_type(EntityType.PRODUCTS) from pygrocy.grocy_api_client import ProductData @@ -145,6 +153,83 @@ def consume_product( product_id, amount, spoiled, transaction_type ) + def inventory_product( + self, + product_id: int, + new_amount: int, + best_before_date: datetime = None, + shopping_location_id: int = None, + location_id: int = None, + price: int = None, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.inventory_product( + product_id, + new_amount, + best_before_date, + shopping_location_id, + location_id, + price, + ) + ) + + if get_details: + product.get_details(self._api_client) + return product + + def add_product_by_barcode( + self, + barcode: str, + amount: float, + price: float, + best_before_date: datetime = None, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.add_product_by_barcode( + barcode, amount, price, best_before_date + ) + ) + + if get_details: + product.get_details(self._api_client) + return product + + def consume_product_by_barcode( + self, + barcode: str, + amount: float = 1, + spoiled: bool = False, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.consume_product_by_barcode(barcode, amount, spoiled) + ) + + if get_details: + product.get_details(self._api_client) + return product + + def inventory_product_by_barcode( + self, + barcode: str, + new_amount: int, + best_before_date: datetime = None, + location_id: int = None, + price: int = None, + get_details: bool = True, + ) -> Product: + product = Product( + self._api_client.inventory_product_by_barcode( + barcode, new_amount, best_before_date, location_id, price + ) + ) + + if get_details: + product.get_details(self._api_client) + return product + def shopping_list(self, get_details: bool = False) -> List[ShoppingListProduct]: raw_shoppinglist = self._api_client.get_shopping_list() shopping_list = [ShoppingListProduct(resp) for resp in raw_shoppinglist] @@ -158,10 +243,14 @@ def add_missing_product_to_shopping_list(self, shopping_list_id: int = 1): return self._api_client.add_missing_product_to_shopping_list(shopping_list_id) def add_product_to_shopping_list( - self, product_id: int, shopping_list_id: int = None, amount: int = None + self, + product_id: int, + shopping_list_id: int = None, + amount: int = None, + quantity_unit_id: int = None, ): return self._api_client.add_product_to_shopping_list( - product_id, shopping_list_id, amount + product_id, shopping_list_id, amount, quantity_unit_id ) def clear_shopping_list(self, shopping_list_id: int = 1): diff --git a/pygrocy/grocy_api_client.py b/pygrocy/grocy_api_client.py index ddf4ddb..edcea1c 100644 --- a/pygrocy/grocy_api_client.py +++ b/pygrocy/grocy_api_client.py @@ -84,7 +84,7 @@ class ProductData(BaseModel): picture_file_name: Optional[str] = None allow_partial_units_in_stock: Optional[bool] = False row_created_timestamp: datetime - min_stock_amount: Optional[int] + min_stock_amount: Optional[float] default_best_before_days: int @@ -145,13 +145,14 @@ class ProductBarcodeData(BaseModel): class ProductDetailsResponse(BaseModel): last_purchased: Optional[date] = None - last_used: Optional[datetime] = None + last_used: Optional[date] = None stock_amount: int stock_amount_opened: int next_best_before_date: Optional[date] = None last_price: Optional[float] = None product: ProductData quantity_unit_stock: QuantityUnitData + default_quantity_unit_purchase: QuantityUnitData barcodes: Optional[List[ProductBarcodeData]] = Field(alias="product_barcodes") location: Optional[LocationData] = None @@ -223,6 +224,19 @@ class MealPlanSectionResponse(BaseModel): row_created_timestamp: datetime +class StockLogResponse(BaseModel): + id: int + product_id: int + amount: int + best_before_date: date + purchased_date: date + used_date: Optional[date] = None + spoiled: bool = False + stock_id: str + transaction_id: str + transaction_type: TransactionType + + def _enable_debug_mode(): _LOGGER.setLevel(logging.DEBUG) @@ -233,13 +247,17 @@ def __init__( base_url, api_key, port: int = DEFAULT_PORT_NUMBER, + path: str = None, verify_ssl=True, debug=False, ): if debug: _enable_debug_mode() - self._base_url = "{}:{}/api/".format(base_url, port) + if path: + self._base_url = "{}:{}/{}/api/".format(base_url, port, path) + else: + self._base_url = "{}:{}/api/".format(base_url, port) _LOGGER.debug(f"generated base url: {self._base_url}") self._api_key = api_key @@ -331,6 +349,12 @@ def get_product(self, product_id) -> ProductDetailsResponse: if parsed_json: return ProductDetailsResponse(**parsed_json) + def get_product_by_barcode(self, barcode) -> ProductDetailsResponse: + url = f"stock/products/by-barcode/{barcode}" + parsed_json = self._do_get_request(url) + if parsed_json: + return ProductDetailsResponse(**parsed_json) + def get_chores(self) -> List[CurrentChoreResponse]: parsed_json = self._do_get_request("chores") return [CurrentChoreResponse(**chore) for chore in parsed_json] @@ -390,6 +414,114 @@ def consume_product( self._do_post_request(f"stock/products/{product_id}/consume", data) + def inventory_product( + self, + product_id: int, + new_amount: float, + best_before_date: datetime = None, + shopping_location_id: int = None, + location_id: int = None, + price: int = None, + ): + data = { + "new_amount": new_amount, + } + + if best_before_date is not None: + data["best_before_date"] = localize_datetime(best_before_date).strftime( + "%Y-%m-%d" + ) + if shopping_location_id is not None: + data["shopping_location_id"] = shopping_location_id + + if location_id is not None: + data["location_id"] = location_id + + if price is not None: + data["price"] = price + + parsed_json = self._do_post_request( + f"stock/products/{product_id}/inventory", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + + def add_product_by_barcode( + self, + barcode: str, + amount: float, + price: float, + best_before_date: datetime = None, + ) -> StockLogResponse: + data = { + "amount": amount, + "transaction_type": TransactionType.PURCHASE.value, + "price": price, + } + + if best_before_date is not None: + data["best_before_date"] = localize_datetime(best_before_date).strftime( + "%Y-%m-%d" + ) + + parsed_json = self._do_post_request( + f"stock/products/by-barcode/{barcode}/add", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + + def consume_product_by_barcode( + self, barcode: str, amount: float = 1, spoiled: bool = False + ): + data = { + "amount": amount, + "spoiled": spoiled, + "transaction_type": TransactionType.CONSUME.value, + } + + parsed_json = self._do_post_request( + f"stock/products/by-barcode/{barcode}/consume", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + + def inventory_product_by_barcode( + self, + barcode: str, + new_amount: float, + best_before_date: datetime = None, + location_id: int = None, + price: int = None, + ): + data = { + "new_amount": new_amount, + } + + if best_before_date is not None: + data["best_before_date"] = localize_datetime(best_before_date).strftime( + "%Y-%m-%d" + ) + + if location_id is not None: + data["location_id"] = location_id + + if price is not None: + data["price"] = price + + parsed_json = self._do_post_request( + f"stock/products/by-barcode/{barcode}/inventory", data + ) + + if parsed_json: + stockLog = [StockLogResponse(**response) for response in parsed_json] + return stockLog[0] + def get_shopping_list(self) -> List[ShoppingListItem]: parsed_json = self._do_get_request("objects/shopping_list") return [ShoppingListItem(**response) for response in parsed_json] @@ -402,13 +534,20 @@ def add_missing_product_to_shopping_list(self, shopping_list_id: int = None): self._do_post_request("stock/shoppinglist/add-missing-products", data) def add_product_to_shopping_list( - self, product_id: int, shopping_list_id: int = 1, amount: int = 1 + self, + product_id: int, + shopping_list_id: int = 1, + amount: int = 1, + quantity_unit_id: int = None, ): data = { "product_id": product_id, "list_id": shopping_list_id, "product_amount": amount, } + if quantity_unit_id: + data["qu_id"] = quantity_unit_id + print(data) self._do_post_request("stock/shoppinglist/add-product", data) def clear_shopping_list(self, shopping_list_id: int = 1): diff --git a/requirements-dev.txt b/requirements-dev.txt index 1f4c2a6..8b60ac4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -responses~=0.13.4 +responses~=0.18.0 pre-commit isort pytest diff --git a/setup.py b/setup.py index c0efc1e..a9be9fa 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pygrocy", - version="1.0.0", + version="1.1.0", author="Sebastian Rutofski", author_email="kontakt@sebastian-rutofski.de", description="", @@ -16,11 +16,11 @@ packages=setuptools.find_packages(), install_requires=[ "requests", - "iso8601~=0.1.16", + "iso8601>=0.1.16,<1.1.0", "pytz~=2021.1", - "tzlocal>=2.1,<3.0", + "tzlocal>=2.1,<5.0", "deprecation~=2.1.0", - "pydantic~=1.8.2", + "pydantic>=1.8.2,<1.10.0", ], classifiers=[ "Programming Language :: Python :: 3", diff --git a/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_error.yaml new file mode 100644 index 0000000..b69559e --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_error.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"amount": 1, "transaction_type": "purchase", "price": 5}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '57' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/555/add + response: + body: + string: '{"error_message":"No product with barcode 555 found"}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 09:41:06 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_valid.yaml new file mode 100644 index 0000000..ce06372 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_add_product_by_barcode_valid.yaml @@ -0,0 +1,105 @@ +interactions: +- request: + body: '{"amount": 1, "transaction_type": "purchase", "price": 5}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '57' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/42141099/add + response: + body: + string: !!binary | + H4sIAAAAAAAEA21Q207EIBD9FcPzblJwrdpfMYbQYdYltgwZIMYY/12mxN2a+Macw8y5vHyp4NWk + 9Emrg0pMvkKxG3RqgFupxiJ8G2bMxc54JkbrXcEGm8Hoo9ZHcy/bleHiMvp/2XojYl2Wg8qJwoIi + PrTlXAjeu/Con2EecESHMDaqsIvZQQkUbflMovsrJaocQKCH9q7RU5RBLvbBlrA2325Nauq6lDBe + PXaI6cMCY8vk9/938e60mYyZ9FM7vBC4zcy1JkYICTf3/SAQMy63Xx3dB9l2JamHs9PuEeRyL0Hc + CN2X8oVSCvHN/pHtXOuUe2dafb/+AANkFP3MAQAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYDnOLAUG6l97Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMGhnvLxbD2 + Re7M7PJNOG+bpKLYvgndiK1YiXsxygHp73evgwv03WBQXruo7Si2YzLmfk6DztvkIGdWFClV1HvO + 5Q9jleSc4ubCobfO6bGDD65S8SVRHLjkVS8Dl3ikhGIM0arns6WlU6w/hUK0MEdUD0vKGvRYLCAH + m0ZqTlRsb7CVyUTYYeCf1nqERh4D+a+5QbYRPViH1P4tka1HfKU2b4uOvTycg51WMdG9Wm0QChEF + HxzljkxRkvOAuusj9HJszDn1wsUnZyhGG0H1qJ4nQNpkqLIZcIxA/YNHpR3OCDgqTo5JE5m3criS + xnqd46qaiVFpSEZGhL+xBttCSLu5xly5SXT1o5ul8ZI0XUnZMaQBL1jKd+51g0CyyaSC3aPfazxM + YHp7AOWRjm4g6oGYlIMjX72sq0VVLer6rnrc1sttXV8w7rymvkpBI3dopmrSGKqXLeCI4zRqVgur + gbSaTAMM4I7o8PZ1ov/9rP6d9MrSdIjtz2l8GJtL+PI8TWFUeFVXq2q52VBUlvY8TrNOC9rXxsRI + 0i61owjKEv1/iFA/U+b7LxrRXG2aOR7/E4zcSPamgP84CpDzpcWa+ym2vTSJSa6/VQ/rk7VE5gnK + xTK+PNQT9SC7zmPHpFLuepNlcHnIlPoxrih8HuqXJMeo4zFzeFoOp73G/Ux77YekbfLpVrsZyoo7 + 5nLgTPKS5cRVA1mLgYdrIF0wRSSYj5fLnX2Rm13qSTBjct/N+hKrh6cnMllDGj+pbrbmzGtSnZm5 + FjPi7wi8HBoiP+uv2iyWNMkM8PxKnKC6fJxo9x3vVHI7K31DwZ88UjfTWfNzpQNNOS1u9HSPpSDW + JK0e2SGEHk0LRrfzc7F4rEnwzmoDnpcgrQ5Fi1Nsn9a5zlnO593GmqeXjbaxNg0po5Um4PsfAAPk + yIEHAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_error.yaml new file mode 100644 index 0000000..9ac898b --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_error.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '{"amount": 1, "spoiled": false, "transaction_type": "consume"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '62' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/555/consume + response: + body: + string: '{"error_message":"No product with barcode 555 found"}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:07:20 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_valid.yaml new file mode 100644 index 0000000..2b0f9ec --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_consume_product_by_barcode_valid.yaml @@ -0,0 +1,105 @@ +interactions: +- request: + body: '{"amount": 1, "spoiled": false, "transaction_type": "consume"}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '62' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/42141099/consume + response: + body: + string: !!binary | + H4sIAAAAAAAEA3VQy07EMAz8FZTzFtWhLLS/glCUJl42oo2jPIQQ4t9xErG7HDh6xh7PzMuXcFYs + AiYpDiJEssVk1aCJAb1T8Zn5AXhaMWW14okiKqszMi5HmIcRBpjreYnmrBPaKyuZgkE+MFv+I1Ig + t2F1MfJaymTeu4MjzMYeR1gnsNVejtonbbIjr/JnqP8N+VR2rM+jMxWB+7l6Kd6Sr3PV7IPKbucE + eg9i8WXbDoIC+ovbDkX6UCYip7O3+xz0N8odyEXKBZ5ZeCOjm51LYxGNC9j8d0FDMeJ23erobZR2 + 27Kak36cYH5i5V5DdVPpfpTOFILzb+rP285xu7G3BuL79QeC7pDY1wEAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81V227bMAz9lULPTRG7ydbkdT+w92EgFJm2hcqWqkuytOi/j5StJN2KIHvbS9Hw + ZvKcQ+pNOG+bpKLYvgndiK1YiXsxygHp329eBxfod4NBee2itqPYjsmY+5IGnbfJQc6sKFKqqPec + yz+MVZJzJjcXDr11To8dfHBNFV8SxYFLXvUycIlHSpiMIVr1fLa09BXrT6EQLZSI6mFJWYMeJwvI + waaRhhMV2xtsZTIRdhj4T2s9QiOPgfzX3CDbiB6sQxr/lsjWI77SmLdFx14ezsFOq5ior1YbhImI + CR8c5Y5MUZLzgLrrI/RybMw59cLFX85QjDaC6lE9z4C0yVBlM+AYgeYHj0o7LAg4Kk6OWROZt+nj + ShrrdY6raiZGpSEZGRH+xBpsCyHtSo1SuUnU+tEVabwkTS0pO4Y04AVLuedeNwgkm0wq2D36vcbD + DKa3B1Ae6dMNRD0Qk3Jw5KuXdbWoqkVd31WP23q5resLxp3XNNdU0MgdmrmaNIbqZQs44jiNmtXC + aiCtJtMAA7gjOrx9nel/P6t/J72ytB1i+2NeH8bmEr68T3MYFV7V1apabjYUlaVd1qnodEL72poY + SdqlcRRBOUX/GyI0z5z5/pNWNFebd47X/wQjD5K9KeBfjgnI0rRYPzFevKSwlyYxyfXXzcOaj8Bl + aF6hXC0DnBOmIiC7zmPHrFLy+inr4JPUj3GTxMtWvyQ5Rh2PmcTTdTgdNh5oPmzfJZ2TT8/azVhW + TCGXA2eSl6wnrhrIOhl4uwYSBnNEivnYXJ7sP+nsUlBiTf3LfVcEJlYPT7xG1pDIT7Ir1px5TauF + mWsxI/6KwNehIfKzAKvNYkmrzACXZ+IE1eXrRMfveKeS21npGwr+5JW6mc6apaoDrTldbvTUx1IQ + a5Juj+wQQo+mBaPb8l4sHmuStrPagOcrSLdD0eUU2y/LXOcs5/NxY83T00bnWJuGlNFKE/D9N9N4 + xRmCBwAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 12:22:18 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_error.yaml new file mode 100644 index 0000000..93cf8f0 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_error.yaml @@ -0,0 +1,99 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/by-barcode/42141099 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYDnOLBku0187Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMagXvrxbD2 + Re7M7PJNOG+bpKLYvgndiK1Yi3sxygHp7zevgwv03WBQXruo7Si2YzLmfk6DztvkIGdWFClV1HvO + 5Q9jleSc4ubCobfO6bGDD65S8SVRHLjkVS8Dl1hRQjGGaNXz2dLSKdafQiFamCOqhyVlDXosFpCD + TSM1Jyq2N9jKZCLsMPBPaz1CI4+B/J+5QbYRPViH1P4tka1HfKU2b4uOvTycg51WMdG9Wm0QChEF + HxzljkxRkvOAuusj9HJszDn1wsUnZyhGG0H1qJ4nQNpkqLIZcIxA/YNHpR3OCDgqTo5JE5m3criS + xnqd46qaiVFpSEZGhD+xBttCSLu5xly5SXT1o5ul8ZI0XUnZMaQBL1jKd+51g0CyyaSC3aPfazxM + YHp7AOWRjm4g6oGYlIMjX72sq0VVLer6rlpt6+W2ri8Yd15TX6WgkTs0UzVpDNXLFnDEcRo1q4XV + QFpNpgEGcEd0ePs60f9+Vv9OemVpOsT2xzQ+jM0lfHmepjAqvK6rdbV8eqKoLO15nGadFrQ/GxMj + SbvUjiIoS/S/IUL9TJnvP2lEc7Vp5nj8TzByI9mbAv7lKEDOlxbrDePFQwp7aRKTvHl8LAK8DM0j + lKtlgHNCKQKy6zx2zCqjtMk6uJL6Ma6cME/1S5Jj1PGYSTxth9Ni44amxfZd0jq5utZuxrJiCrkc + OJO8ZD1x1UDWYuDpGkgYzBEp5uPlcmf/yc0uBSWqDTMj990sMVGtCsrWkM5PyjuZc/Z1veY3YCbn + ekgR8Ii/IvCCaIj/rMHqabHcLJb8ZMwvxQmtyweK9t/xTiW3s9I3FHzlobqZ0ZqfLB1o0ml5o6d7 + LAURJ2n9yA4h9GhaMLqdn4xN9eWRVO+sNuB5E9L+ULQ9xXb19WGTS51FfV5xjC89cLSUtWlIH600 + Ad9/A5ls1FuIBwAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:58 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 45, "best_before_date": "2019-05-04", "shopping_location_id": + 1, "location_id": 150, "price": true}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '114' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/4/inventory + response: + body: + string: '{"error_message":"The new amount cannot equal the current stock amount"}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:58 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_valid.yaml new file mode 100644 index 0000000..711a403 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_by_barcode_valid.yaml @@ -0,0 +1,160 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/by-barcode/42141099 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYDnOLBku0187Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMagXvrxbD2 + Re7M7PJNOG+bpKLYvgndiK1Yi3sxygHp7zevgwv03WBQXruo7Si2YzLmfk6DztvkIGdWFClV1HvO + 5Q9jleSc4ubCobfO6bGDD65S8SVRHLjkVS8Dl1hRQjGGaNXz2dLSKdafQiFamCOqhyVlDXosFpCD + TSM1Jyq2N9jKZCLsMPBPaz1CI4+B/J+5QbYRPViH1P4tka1HfKU2b4uOvTycg51WMdG9Wm0QChEF + HxzljkxRkvOAuusj9HJszDn1wsUnZyhGG0H1qJ4nQNpkqLIZcIxA/YNHpR3OCDgqTo5JE5m3criS + xnqd46qaiVFpSEZGhD+xBttCSLu5xly5SXT1o5ul8ZI0XUnZMaQBL1jKd+51g0CyyaSC3aPfazxM + YHp7AOWRjm4g6oGYlIMjX72sq0VVLer6rlpt6+W2ri8Yd15TX6WgkTs0UzVpDNXLFnDEcRo1q4XV + QFpNpgEGcEd0ePs60f9+Vv9OemVpOsT2xzQ+jM0lfHmepjAqvK6rdbV8eqKoLO15nGadFrQ/GxMj + SbvUjiIoS/S/IUL9TJnvP2lEc7Vp5nj8TzByI9mbAv7lKEDOlxbrDePFQwp7aRKTvHl8LAK8DM0j + lKtlgHNCKQKy6zx2zCqjtMk6uJL6Ma6cME/1S5Jj1PGYSTxth9Ni44amxfZd0jq5utZuxrJiCrkc + OJO8ZD1x1UDWYuDpGkgYzBEp5uPlcmf/yc0uBSWqDTMj990sMVGtCsrWkM5PyjuZc/Z1veY3YCbn + ekgR8Ii/IvCCaIj/rMHqabHcLJb8ZMwvxQmtyweK9t/xTiW3s9I3FHzlobqZ0ZqfLB1o0ml5o6d7 + LAURJ2n9yA4h9GhaMLqdn4xN9eWRVO+sNuB5E9L+ULQ9xXb19WGTS51FfV5xjC89cLSUtWlIH600 + Ad9/A5ls1FuIBwAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:58 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 55, "best_before_date": "2019-05-04", "location_id": 1, + "price": 150}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '84' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/by-barcode/42141099/inventory + response: + body: + string: !!binary | + H4sIAAAAAAAEA2VQ227DIAz9lYnnRoJc1ia/Mk2IgLOipRhx2VRN+/dh0JZKe/Q5ts/l5YtZwxYm + LpydmA9osk6yQmMB1A2zS8QTvUJMcoUNA0ijEhS852Lu+NRx2vY56KuKYA62F50QXT8UNh+Ey/t+ + YtGj3YHU6XlMqN+b8rOY9QabWodRE5WCclHpZNHJdPeka90HuITh3mkMASpHDoLVRIuJ7rIz6Gg8 + BpnsraRQN8+W5gI9uD/HDQr4KXWAktA87pewv3GexLhMwzLNRWVHraq1VmRBih/roWZpD6vH/dhq + 6GOseltyG84vZ6HUWZc/rRJyQ3Q7ilf03ro3+V+2FBxagYJ9v/4A9PKThtoBAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnODBlq0l07Q/0XhQLmqIkIpTI8GHXCfLv3SUl22mNwL31Ylj7 + IndmdvnGnLdtkpE1b0y3rGFbdscmMSr8+83r4AJ+typIr13UdmLNlIy5W9Kg9zY5yJkcI4WMek+5 + 9GGsFJRT3FQ4DNY5PfXwwVUqviSMA5e8HESgEhtMKMYQrXw+Wzo8xfpTKEQLSwS/X2PWqKdiATHa + NGFzjJO9VZ1IJsJOBfrprFfQimNA/2duEF1UHqxT2P4tkZ1X6hXbvC06DuJwDnZaxoT36rRRUIgo + +KhJ7NAUBToPSvdDhEFMrTmnXrjo5AzFZCPIQcnnGZAuGaxsRjVFwP7BK6mdWhBwWBwdsyYyb+Vw + KYz1OsfxioiRaUxGRAV/Yg22g5B2S42lcpvw6ke3SOMlabyStFNIo7pgKd950K0ClE0mFexe+b1W + hxlMbw8gvcKjW4h6RCbF6NBXrSu+4nxVVV/4pqnWTVVdMO68xr5KQSN2yszVhDFYL1vAIcdp0qQW + UgNqNZkWCMAd0uHt60z/+1n9O+GlxelgzY95fAibS/jyPM1hWHhb8S1fPz1hVJb2Mk6LTgvan42J + EahdbEcilCX63xDBfubM9584ornaPHM0/icYqZHsTUH95ShALpdmdU140ZDCXphEJD9sHosAL0Pz + COVqGeCcUIqA6HuvemIVk+s66+BK6se4csIy1S9JTFHHYybxtB1Oi40amhfbd4Hr5OpauxlLThRS + OXAmeUF6oqoBrcVA0zWiMIgjVMzHy+XO/pObXQqK8ZqYEft+kRjjm20mwxrU+Ul5J3POvq7X/AYs + 5FwPKQKe1K8ItCBa5D9rkD+t1vVqTU/G8lKc0Lp8oHD/Hb/I5HZW+BaDrzxUNzNa0ZOlA046Lm/l + 8R5rhsQJXD+iVxAGZTowuluejJp/fUTVO6sNeNqEuD8kbk/WbB7u61zqLOrziiN88YHDpaxNi/ro + hAnq/TcsMeFEiAcAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_error.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_error.yaml new file mode 100644 index 0000000..fb44351 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_error.yaml @@ -0,0 +1,99 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnODBlq0l07Q/0XhQLmqIkIpTI8GHXCfLv3SUl22mNwL31Ylj7 + IndmdvnGnLdtkpE1b0y3rGFbdscmMSr8+83r4AJ+typIr13UdmLNlIy5W9Kg9zY5yJkcI4WMek+5 + 9GGsFJRT3FQ4DNY5PfXwwVUqviSMA5e8HESgEhtMKMYQrXw+Wzo8xfpTKEQLSwS/X2PWqKdiATHa + NGFzjJO9VZ1IJsJOBfrprFfQimNA/2duEF1UHqxT2P4tkZ1X6hXbvC06DuJwDnZaxoT36rRRUIgo + +KhJ7NAUBToPSvdDhEFMrTmnXrjo5AzFZCPIQcnnGZAuGaxsRjVFwP7BK6mdWhBwWBwdsyYyb+Vw + KYz1OsfxioiRaUxGRAV/Yg22g5B2S42lcpvw6ke3SOMlabyStFNIo7pgKd950K0ClE0mFexe+b1W + hxlMbw8gvcKjW4h6RCbF6NBXrSu+4nxVVV/4pqnWTVVdMO68xr5KQSN2yszVhDFYL1vAIcdp0qQW + UgNqNZkWCMAd0uHt60z/+1n9O+GlxelgzY95fAibS/jyPM1hWHhb8S1fPz1hVJb2Mk6LTgvan42J + EahdbEcilCX63xDBfubM9584ornaPHM0/icYqZHsTUH95ShALpdmdU140ZDCXphEJD9sHosAL0Pz + COVqGeCcUIqA6HuvemIVk+s66+BK6se4csIy1S9JTFHHYybxtB1Oi40amhfbd4Hr5OpauxlLThRS + OXAmeUF6oqoBrcVA0zWiMIgjVMzHy+XO/pObXQqK8ZqYEft+kRjjm20mwxrU+Ul5J3POvq7X/AYs + 5FwPKQKe1K8ItCBa5D9rkD+t1vVqTU/G8lKc0Lp8oHD/Hb/I5HZW+BaDrzxUNzNa0ZOlA046Lm/l + 8R5rhsQJXD+iVxAGZTowuluejJp/fUTVO6sNeNqEuD8kbk/WbB7u61zqLOrziiN88YHDpaxNi/ro + hAnq/TcsMeFEiAcAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 55, "best_before_date": "2019-05-04", "shopping_location_id": + 1, "location_id": 1, "price": 150}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '111' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/4/inventory + response: + body: + string: '{"error_message":"The new amount cannot equal the current stock amount"}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/cassettes/test_grocy/TestGrocy.test_inventory_product_valid.yaml b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_valid.yaml new file mode 100644 index 0000000..ce841b7 --- /dev/null +++ b/test/cassettes/test_grocy/TestGrocy.test_inventory_product_valid.yaml @@ -0,0 +1,160 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnODBlq0l07Q/0XhQLmqIkIpTI8GHXCfLv3SUl22mNwL31Ylj7 + IndmdvnGnLdtkpE1b0y3rGFbdscmMSr8+83r4AJ+typIr13UdmLNlIy5W9Kg9zY5yJkcI4WMek+5 + 9GGsFJRT3FQ4DNY5PfXwwVUqviSMA5e8HESgEhtMKMYQrXw+Wzo8xfpTKEQLSwS/X2PWqKdiATHa + NGFzjJO9VZ1IJsJOBfrprFfQimNA/2duEF1UHqxT2P4tkZ1X6hXbvC06DuJwDnZaxoT36rRRUIgo + +KhJ7NAUBToPSvdDhEFMrTmnXrjo5AzFZCPIQcnnGZAuGaxsRjVFwP7BK6mdWhBwWBwdsyYyb+Vw + KYz1OsfxioiRaUxGRAV/Yg22g5B2S42lcpvw6ke3SOMlabyStFNIo7pgKd950K0ClE0mFexe+b1W + hxlMbw8gvcKjW4h6RCbF6NBXrSu+4nxVVV/4pqnWTVVdMO68xr5KQSN2yszVhDFYL1vAIcdp0qQW + UgNqNZkWCMAd0uHt60z/+1n9O+GlxelgzY95fAibS/jyPM1hWHhb8S1fPz1hVJb2Mk6LTgvan42J + EahdbEcilCX63xDBfubM9584ornaPHM0/icYqZHsTUH95ShALpdmdU140ZDCXphEJD9sHosAL0Pz + COVqGeCcUIqA6HuvemIVk+s66+BK6se4csIy1S9JTFHHYybxtB1Oi40amhfbd4Hr5OpauxlLThRS + OXAmeUF6oqoBrcVA0zWiMIgjVMzHy+XO/pObXQqK8ZqYEft+kRjjm20mwxrU+Ul5J3POvq7X/AYs + 5FwPKQKe1K8ItCBa5D9rkD+t1vVqTU/G8lKc0Lp8oHD/Hb/I5HZW+BaDrzxUNzNa0ZOlA046Lm/l + 8R5rhsQJXD+iVxAGZTowuluejJp/fUTVO6sNeNqEuD8kbk/WbB7u61zqLOrziiN88YHDpaxNi/ro + hAnq/TcsMeFEiAcAAA== + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: '{"new_amount": 65, "best_before_date": "2019-05-04", "shopping_location_id": + 1, "location_id": 1, "price": 150}' + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '111' + Content-Type: + - application/json + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: POST + uri: https://localhost/api/stock/products/4/inventory + response: + body: + string: !!binary | + H4sIAAAAAAAEA2VQ20rEMBD9FcnzFpJe3La/IhLSZOoG20zIRVnEf3fSoF3wcc6ZmXN5+WLWsJmJ + UbAL8wFN1kkeUE+A2jG7VHhO0wIxyQVWDCCNSkB4y8XU8KHhZdvnoG8qgjnZVjRCNG1HbD4Jl7ft + wqJHu0FRL89jQv1elZ/FpFdY1dL1ulApKBeVThadTHdfdK37AJcw3BuNIcDBFQfB6kKLodxlZ9CV + 8RxksjulULtnc3WBHtyf4woF/JQ6ACU0j/sU9jfOk+jnoZuHiVQ21OqwVoskhPxYD0eW+vDwuJ1b + FX2MddxSbsP5eB0V7670p1ZS3BS6HsUbem/dm/wvSwWHWqBg368/Isamy9oBAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/4 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lYLnOLDk2E187Q/0XhQLmlpJRCiR4cOuE+Tfu0tKttMagXvrxbD2 + Re7M7PJNOG+bpKLYvgndiK14EHdilAPS329eBxfou8GgvHZR21Fsx2TM3ZwGnbfJQc6sKFKqqPec + yx/GKsk5xc2FQ2+d02MHH1yl4kuiOHDJq14GLrGihGIM0arns6WlU6w/hUK0MEdU90vKGvRYLCAH + m0ZqTlRsb7CVyUTYYeCf1nqERh4D+T9zg2wjerAOqf1bIluP+Ept3hYde3k4BzutYqJ7tdogFCIK + PjjKHZmiJOcBdddH6OXYmHPqhYtPzlCMNoLqUT1PgLTJUGUz4BiB+gePSjucEXBUnByTJjJv5XAl + jfU6x1U1E6PSkIyMCH9iDbaFkHZzjblyk+jqRzdL4yVpupKyY0gDXrCU79zrBoFkk0kFu0e/13iY + wPT2AMojHd1A1AMxKQdHvnpZV4uqWtT1l2q1rZfbur5g3HlNfZWCRu7QTNWkMVQvW8ARx2nUrBZW + A2k1mQYYwB3R4e3rRP/7Wf076ZWl6RDbH9P4MDaX8OV5msKo8ENdPVTLpyeKytKex2nWaUH7szEx + krRL7SiCskT/GyLUz5T5/pNGNFebZo7H/wQjN5K9KeBfjgLkfGmxWTNePKSwlyYxyY+Pj0WAl6F5 + hHK1DHBOKEVAdp3Hjlml5M066+BK6se4csI81S9JjlHHYybxtB1Oi40bmhbbd0nr5OpauxnLiink + cuBM8pL1xFUDWYuBp2sgYTBHpJiPl8ud/Sc3uxSUqNbMjNx3s8REtdpkMqwhnZ+UdzLn7Ot6zW/A + TM71kCLgEX9F4AXREP9Zg9XTYrleLPnJmF+KE1qXDxTtv+MXldzOSt9Q8JWH6mZGa36ydKBJp+WN + nu6xFEScpPUjO4TQo2nB6HZ+MtbV5pFU76w24HkT0v5QtD3FdvX1fp1LnUV9XnGMLz1wtJS1aUgf + rTQB338DsIaNHogHAAA= + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 23 Nov 2021 14:53:59 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_product/TestProduct.test_get_product_by_barcode.yaml b/test/cassettes/test_product/TestProduct.test_get_product_by_barcode.yaml new file mode 100644 index 0000000..a32d4b1 --- /dev/null +++ b/test/cassettes/test_product/TestProduct.test_get_product_by_barcode.yaml @@ -0,0 +1,55 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/by-barcode/42141099 + response: + body: + string: !!binary | + H4sIAAAAAAAEA81Vy27bMBD8lUDnOLDkGEV87Q/0XhQLmqIkIpRIU6RdJ8i/d5aUbKUwAvfWi2Ht + i9yZ2eV74bytowzF7r3QdbErnovHYhC9wt/vXo9uxHetRum1C9oOxW6IxjzOadR6Gx2lzBKRQgZ9 + 5Fz+MFYKzsluLjx21jk9tPTJlSseIuLIRS87MXKJDRKycQxWvl4tDU6x/hJKwdIcUT6tkdXrIVtI + 9DYOaK4o2V6rRkQTaK9G/mmsV1SL8wj/V24STVCerFNo/57Ixiv1hjbviw6dOF2DnZYh4l6NNooy + ERkfNYg9TEHAeVK67QJ1YqjNNXXh4pMTFIMNJDslXydAmmhQ2fRqCIT+ySupnZoRcCgOx6SJxFs+ + XApjvU5xZcXEyNhHI4Kiv7Em29AY93ONuXIdcfWzm6VxiBpXknYYY68WLKU7d7pWBNkkUskelT9q + dZrA9PZE0iscXVPQPZgUvYOvWlflqixXVfVQbnbVeldVC8ad1+grFzRir8xUTRiDeslCDhzHQbNa + WA3QajQ1MYB70OHt20T/x1X9e+GlxXQUu5/T+DA2S/jSPE1hKPxclc/l+uUFUUna8zjNOs1ofzUm + RkC7aEcCyhz9b4ignynz4xdGNFWbZo7Hf4axZPSSN44KjuliPIhXurYMUzIdhYmJ229PW87M1txV + GhyukWFdlCDRtl61zCW820T+jczPYVnX8ygfohiCDufE3GUlXLYZ0zFtsx8CO+TmLrsbwJJ543Lk + TPSCRcRVR1izgUeqhxoYLcjk8+VSZ//JzZYqKjZP1YapFMd2VhZsbLEG4r7IbTKm3NsSLZYzdzsk + C2lQvwPxTqhBfpZdtVpvV9Xy3bhgtXyTsPLODzK6vRW+xhVvvE1385lO0yOGG/ta+aRQ0CawcUSr + aOyUacjoZn4lqnINaTurDXnefdgYEvuy2MGMMlc1X0eENwneM+xgbWoooxFmVB9/AA9z8G53BwAA + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 22 Nov 2021 13:29:27 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 200 + message: OK +version: 1 diff --git a/test/cassettes/test_product/TestProduct.test_product_by_barcode_get_details_non_existant.yaml b/test/cassettes/test_product/TestProduct.test_product_by_barcode_get_details_non_existant.yaml new file mode 100644 index 0000000..a8fc62d --- /dev/null +++ b/test/cassettes/test_product/TestProduct.test_product_by_barcode_get_details_non_existant.yaml @@ -0,0 +1,40 @@ +interactions: +- request: + body: null + headers: + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.26.0 + accept: + - application/json + method: GET + uri: https://localhost/api/stock/products/by-barcode/200 + response: + body: + string: '{"error_message":"No product with barcode 200 found"}' + headers: + Access-Control-Allow-Headers: + - '*' + Access-Control-Allow-Methods: + - GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Origin: + - '*' + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 22 Nov 2021 13:29:27 GMT + Server: + - nginx/1.20.1 + Transfer-Encoding: + - chunked + X-Powered-By: + - PHP/8.0.11 + status: + code: 400 + message: Bad Request +version: 1 diff --git a/test/test_grocy.py b/test/test_grocy.py index d1bec24..c758942 100644 --- a/test/test_grocy.py +++ b/test/test_grocy.py @@ -4,9 +4,11 @@ from unittest import TestCase from unittest.mock import mock_open, patch +import pytest import responses from pygrocy import Grocy +from pygrocy.data_models.product import Product from pygrocy.errors import GrocyError from pygrocy.grocy_api_client import GrocyApiClient @@ -216,6 +218,86 @@ def test_consume_product_error(self): ) 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) diff --git a/test/test_grocy_api_client.py b/test/test_grocy_api_client.py new file mode 100644 index 0000000..4b7f311 --- /dev/null +++ b/test/test_grocy_api_client.py @@ -0,0 +1,23 @@ +from pygrocy.grocy_api_client import GrocyApiClient + + +class TestGrocyApiClient: + def test_url_only(self): + client = GrocyApiClient(api_key="", base_url="http://grocy.de") + assert client._base_url == "http://grocy.de:9192/api/" + + def test_url_and_port(self): + client = GrocyApiClient(api_key="", base_url="http://grocy.de", port=1234) + assert client._base_url == "http://grocy.de:1234/api/" + + def test_url_and_port_and_path(self): + client = GrocyApiClient( + api_key="", base_url="http://grocy.de", port=1234, path="my/custom/path" + ) + assert client._base_url == "http://grocy.de:1234/my/custom/path/api/" + + def test_url_and_path(self): + client = GrocyApiClient( + api_key="", base_url="http://grocy.de", path="my/custom/path" + ) + assert client._base_url == "http://grocy.de:9192/my/custom/path/api/" diff --git a/test/test_product.py b/test/test_product.py index 44c52fc..cbe89d2 100644 --- a/test/test_product.py +++ b/test/test_product.py @@ -23,6 +23,11 @@ def test_product_get_details_valid(self, grocy): assert product.name == "Cheese" assert product.available_amount == 5 assert product.product_group_id == 6 + assert product.qu_factor_purchase_to_stock == 1.0 + assert product.default_quantity_unit_purchase.id == 3 + assert product.default_quantity_unit_purchase.name == "Pack" + assert product.default_quantity_unit_purchase.description == None + assert product.default_quantity_unit_purchase.name_plural == "Packs" assert len(product.product_barcodes) == 1 barcode = product.product_barcodes[0] @@ -56,3 +61,26 @@ def test_add_product_pic_valid(self, grocy, mocker): mocker.patch("builtins.open", mocker.mock_open()) assert grocy.add_product_pic(20, "/somepath/pic.jpg") is None + + @pytest.mark.vcr + def test_get_product_by_barcode(self, grocy): + product = grocy.product_by_barcode("42141099") + + assert isinstance(product, Product) + assert product.name == "Crisps" + assert product.available_amount == 5 + assert product.product_group_id == 1 + + assert len(product.product_barcodes) == 1 + barcode = product.product_barcodes[0] + assert isinstance(barcode, ProductBarcode) + assert barcode.barcode == "42141099" + + @pytest.mark.vcr + def test_product_by_barcode_get_details_non_existant(self, grocy): + with pytest.raises(GrocyError) as exc_info: + grocy.product_by_barcode(200) + + error = exc_info.value + assert error.status_code == 400 + assert error.message == "No product with barcode 200 found" diff --git a/tox.ini b/tox.ini index 67b3705..470bd4a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] -envlist = py36,py38,py39 +envlist = py38,py39,py310 [testenv] passenv = TRAVIS TRAVIS_* deps = -rrequirements.txt -rrequirements-dev.txt -commands = pytest --cov=pygrocy --cov-append \ No newline at end of file +commands = pytest --cov=pygrocy --cov-append