producteca 1.0.14__py3-none-any.whl → 2.0.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,11 @@
1
- from pydantic import BaseModel, PrivateAttr
2
- from abc import ABC, abstractmethod
3
- from ..config.config import ConfigProducteca, APIConfig
1
+ from abc import ABC
2
+ from ..config.config import ConfigProducteca
3
+ from dataclasses import dataclass
4
+ from typing import Optional
4
5
 
5
6
 
6
- class AbstractProductecaModel(BaseModel, ABC):
7
- _config: APIConfig = PrivateAttr()
8
-
9
- @property
10
- @abstractmethod
11
- def endpoint(self) -> str:
12
- pass
13
-
14
- def dict(self, *args, **kwargs):
15
- return super().dict(*args, exclude_none=True, **kwargs)
16
-
17
-
18
- class AbstractProductecaV1Model(AbstractProductecaModel):
19
- _config: ConfigProducteca = PrivateAttr()
20
-
21
- @property
22
- def endpoint_url(self) -> str:
23
- return self._config.get_endpoint(self.endpoint)
7
+ @dataclass
8
+ class BaseService[T](ABC):
9
+ config: ConfigProducteca
10
+ endpoint: str
11
+ _record: Optional[T] = None
producteca/client.py CHANGED
@@ -1,37 +1,22 @@
1
1
  from producteca.config.config import ConfigProducteca
2
- from producteca.products.products import Product
3
- from producteca.sales_orders.sales_orders import SalesOrder
4
- from producteca.search.search import Search
5
- from producteca.shipments.shipment import Shipment
6
- from producteca.payments.payments import Payment
2
+ from producteca.products.products import ProductService
3
+ from producteca.sales_orders.sales_orders import SaleOrderService
7
4
  import os
8
5
 
9
6
 
10
7
  class ProductecaClient:
11
8
 
12
- def __init__(self, token: str = os.environ['PRODUCTECA_TOKEN'], api_key: str = os.environ['PRODUCTECA_API_KEY']):
9
+ def __init__(self, token: str = os.environ.get('PRODUCTECA_TOKEN', ''), api_key: str = os.environ.get('PRODUCTECA_API_KEY', '')):
13
10
  if not token:
14
11
  raise ValueError('PRODUCTECA_TOKEN environment variable not set')
15
12
  if not api_key:
16
13
  raise ValueError('PRODUCTECA_API_KEY environment variable not set')
17
14
  self.config = ConfigProducteca(token=token, api_key=api_key)
18
-
19
- @property
20
- def Product(self):
21
- return lambda *args: Product(config=self.config, *args)
22
-
23
- @property
24
- def SalesOrder(self):
25
- return lambda *args: SalesOrder(config=self.config, *args)
26
15
 
27
16
  @property
28
- def Search(self):
29
- return lambda *args: Search(config=self.config, *args)
30
-
31
- @property
32
- def Shipment(self):
33
- return lambda *args: Shipment(config=self.config, *args)
17
+ def Product(self):
18
+ return ProductService(self.config)
34
19
 
35
20
  @property
36
- def Payment(self):
37
- return lambda *args: Payment(config=self.config, *args)
21
+ def SalesOrder(self):
22
+ return SaleOrderService(self.config)
@@ -1 +0,0 @@
1
- from .config import ConfigProducteca
@@ -1,7 +1,5 @@
1
1
  from pydantic import BaseModel
2
2
  from typing import Optional
3
- import requests
4
- from ..config.config import ConfigProducteca
5
3
 
6
4
 
7
5
  class PaymentCard(BaseModel):
@@ -32,14 +30,4 @@ class Payment(BaseModel):
32
30
  hasCancelableStatus: bool
33
31
  id: Optional[int] = None
34
32
 
35
- @classmethod
36
- def create(cls, config: ConfigProducteca, sale_order_id: int, payload: "Payment") -> "Payment":
37
- url = config.get_endpoint(f"salesorders/{sale_order_id}/payments")
38
- res = requests.post(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
39
- return cls(**res.json())
40
-
41
- @classmethod
42
- def update(cls, config: ConfigProducteca, sale_order_id: int, payment_id: int, payload: "Payment") -> "Payment":
43
- url = config.get_endpoint(f"salesorders/{sale_order_id}/payments/{payment_id}")
44
- res = requests.put(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
45
- return cls(**res.json())
33
+
@@ -1,13 +1,13 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, Mock
3
3
  from datetime import datetime
4
- from producteca.config.config import ConfigProducteca
5
- from producteca.payments.payments import Payment, PaymentCard, PaymentIntegration
4
+ from producteca.client import ProductecaClient
5
+ from producteca.payments.payments import Payment
6
6
 
7
7
 
8
8
  class TestPayments(unittest.TestCase):
9
9
  def setUp(self):
10
- self.config = ConfigProducteca(token="asd", api_key="test_key")
10
+ self.client = ProductecaClient(token="asd", api_key="test_key")
11
11
  self.sale_order_id = 123
12
12
  self.payment_id = 456
13
13
 
@@ -35,7 +35,7 @@ class TestPayments(unittest.TestCase):
35
35
  payment = Payment(**self.payment_data)
36
36
 
37
37
  # Test create method
38
- result = Payment.create(self.config, self.sale_order_id, payment)
38
+ result = self.client.SalesOrder(id=self.sale_order_id).add_payment(payment)
39
39
 
40
40
  # Assertions
41
41
  mock_post.assert_called_once()
@@ -55,7 +55,7 @@ class TestPayments(unittest.TestCase):
55
55
  payment = Payment(**self.payment_data)
56
56
 
57
57
  # Test update method
58
- result = Payment.update(self.config, self.sale_order_id, self.payment_id, payment)
58
+ result = self.client.SalesOrder(id=self.sale_order_id).update_payment(self.payment_id, payment)
59
59
 
60
60
  # Assertions
61
61
  mock_put.assert_called_once()
@@ -1 +0,0 @@
1
- from . import products
@@ -1,20 +1,23 @@
1
1
  from typing import List, Optional, Union
2
2
  from pydantic import BaseModel, Field
3
- import requests
4
- from ..config.config import ConfigProducteca
3
+ from dataclasses import dataclass
4
+ from producteca.abstract.abstract_dataclass import BaseService
5
+ from producteca.products.search_products import SearchProduct, SearchProductParams
5
6
  import logging
7
+ import requests
6
8
 
7
9
  _logger = logging.getLogger(__name__)
8
10
 
9
- # Models for nested structures
10
11
 
11
12
  class Attribute(BaseModel):
12
13
  key: str
13
14
  value: str
14
15
 
16
+
15
17
  class Tag(BaseModel):
16
18
  tag: str
17
19
 
20
+
18
21
  class Dimensions(BaseModel):
19
22
  weight: Optional[float] = None
20
23
  width: Optional[float] = None
@@ -22,11 +25,13 @@ class Dimensions(BaseModel):
22
25
  length: Optional[float] = None
23
26
  pieces: Optional[int] = None
24
27
 
28
+
25
29
  class Deal(BaseModel):
26
30
  campaign: str
27
31
  regular_price: Optional[float] = Field(default=None, alias='regularPrice')
28
32
  deal_price: Optional[float] = Field(default=None, alias='dealPrice')
29
33
 
34
+
30
35
  class Stock(BaseModel):
31
36
  quantity: Optional[int] = None
32
37
  available_quantity: Optional[int] = Field(default=None, alias='availableQuantity')
@@ -35,15 +40,18 @@ class Stock(BaseModel):
35
40
  reserved: Optional[int] = None
36
41
  available: Optional[int] = None
37
42
 
43
+
38
44
  class Price(BaseModel):
39
45
  amount: Optional[float] = None
40
46
  currency: str
41
47
  price_list: str = Field(alias='priceList')
42
48
  price_list_id: Optional[int] = Field(default=None, alias='priceListId')
43
49
 
50
+
44
51
  class Picture(BaseModel):
45
52
  url: str
46
53
 
54
+
47
55
  class Integration(BaseModel):
48
56
  app: Optional[int] = None
49
57
  integration_id: Optional[str] = Field(default=None, alias='integrationId')
@@ -57,6 +65,7 @@ class Integration(BaseModel):
57
65
  id: Optional[int] = None
58
66
  parent_integration: Optional[str] = Field(default=None, alias='parentIntegration')
59
67
 
68
+
60
69
  class Variation(BaseModel):
61
70
  variation_id: Optional[int] = Field(default=None, alias='variationId')
62
71
  components: Optional[List] = None
@@ -71,11 +80,15 @@ class Variation(BaseModel):
71
80
  sku: Optional[str] = None
72
81
  barcode: Optional[str] = None
73
82
 
74
- # Model base para los productos
83
+
84
+ class MeliCategory(BaseModel):
85
+ meli_id: Optional[str] = Field(default=None, alias='meliId')
86
+ accepts_mercadoenvios: Optional[bool] = Field(default=None, alias='acceptsMercadoenvios')
87
+ suggest: Optional[bool] = None
88
+ fixed: Optional[bool] = None
89
+
90
+
75
91
  class Product(BaseModel):
76
- config: Optional[ConfigProducteca] = Field(default=None, exclude=True)
77
- endpoint: str = Field(default='products', exclude=True)
78
- create_if_it_doesnt_exist: bool = Field(default=False, exclude=True)
79
92
  sku: Optional[str] = None
80
93
  variation_id: Optional[int] = None
81
94
  code: Optional[str] = None
@@ -85,7 +98,7 @@ class Product(BaseModel):
85
98
  tags: Optional[List[str]] = None
86
99
  buying_price: Optional[float] = None
87
100
  dimensions: Optional[Dimensions] = None
88
- category: Optional[Union[str, dict]] = None # Puede ser string en POST o dict en GET Meli
101
+ category: Optional[Union[str, MeliCategory]]
89
102
  brand: Optional[str] = None
90
103
  notes: Optional[str] = None
91
104
  deals: Optional[List[Deal]] = None
@@ -94,60 +107,6 @@ class Product(BaseModel):
94
107
  pictures: Optional[List[Picture]] = None
95
108
 
96
109
 
97
- def create(self):
98
- endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
99
- headers = self.config.headers.copy()
100
- headers.update({"createifitdoesntexist": str(self.create_if_it_doesnt_exist).lower()})
101
- data = self.model_dump_json(by_alias=True, exclude_none=True)
102
- response = requests.post(endpoint_url, data=data, headers=headers)
103
- if response.status_code == 204:
104
- final_response = {"Message":"Product does not exist and the request cant create if it does not exist"}
105
- else:
106
- final_response = response.json()
107
- return final_response, response.status_code
108
-
109
- def update(self):
110
- endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
111
- headers = self.config.headers.copy()
112
- data = self.model_dump_json(by_alias=True, exclude_none=True)
113
- if not self.code and not self.sku:
114
- return {"Message":"Sku or code should be provided to update the product"}, 204
115
- response = requests.post(endpoint_url, data=data, headers=headers)
116
- if response.status_code == 204:
117
- final_response = {"Message":"Product does not exist and the request cant create if it does not exist"}
118
- else:
119
- final_response = response.json()
120
- return final_response, response.status_code
121
-
122
- @classmethod
123
- def get(cls, config: ConfigProducteca, product_id: int):
124
- endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}')
125
- headers = config.headers
126
- response = requests.get(endpoint_url, headers=headers)
127
- response_data = response.json()
128
- return response_data, response.status_code
129
-
130
- @classmethod
131
- def get_bundle(cls, config: ConfigProducteca, product_id: int):
132
- endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}/bundles')
133
- headers = config.headers
134
- response = requests.get(endpoint_url, headers=headers)
135
- return cls(config=config, **response.json()), response.status_code
136
-
137
- @classmethod
138
- def get_ml_integration(cls, config: ConfigProducteca, product_id: int):
139
- endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}/listintegration')
140
- headers = config.headers
141
- response = requests.get(endpoint_url, headers=headers)
142
- return cls(config=config, **response.json()), response.status_code
143
-
144
- # Modelo con campos extra de la vista Meli
145
- class MeliCategory(BaseModel):
146
- meli_id: Optional[str] = Field(default=None, alias='meliId')
147
- accepts_mercadoenvios: Optional[bool] = Field(default=None, alias='acceptsMercadoenvios')
148
- suggest: Optional[bool] = None
149
- fixed: Optional[bool] = None
150
-
151
110
  class Shipping(BaseModel):
152
111
  local_pickup: Optional[bool] = Field(default=None, alias='localPickup')
153
112
  mode: Optional[str] = None
@@ -156,9 +115,11 @@ class Shipping(BaseModel):
156
115
  mandatory_free_shipping: Optional[bool] = Field(default=None, alias='mandatoryFreeShipping')
157
116
  free_shipping_method: Optional[str] = Field(default=None, alias='freeShippingMethod')
158
117
 
118
+
159
119
  class MShopsShipping(BaseModel):
160
120
  enabled: Optional[bool] = None
161
121
 
122
+
162
123
  class AttributeCompletion(BaseModel):
163
124
  product_identifier_status: Optional[str] = Field(default=None, alias='productIdentifierStatus')
164
125
  data_sheet_status: Optional[str] = Field(default=None, alias='dataSheetStatus')
@@ -166,13 +127,14 @@ class AttributeCompletion(BaseModel):
166
127
  count: Optional[int] = None
167
128
  total: Optional[int] = None
168
129
 
130
+
169
131
  class MeliProduct(Product):
170
132
  product_id: Optional[int] = Field(default=None, alias='productId')
171
133
  has_custom_shipping_costs: Optional[bool] = Field(default=None, alias='hasCustomShippingCosts')
172
134
  shipping: Optional[Shipping] = None
173
135
  mshops_shipping: Optional[MShopsShipping] = Field(default=None, alias='mShopsShipping')
174
136
  add_free_shipping_cost_to_price: Optional[bool] = Field(default=None, alias='addFreeShippingCostToPrice')
175
- category: Optional[MeliCategory] = None
137
+ category: Optional[Union[str, MeliCategory]] # will never be str, but needs compat with super class
176
138
  attribute_completion: Optional[AttributeCompletion] = Field(default=None, alias='attributeCompletion')
177
139
  catalog_products: Optional[List[str]] = Field(default=None, alias='catalogProducts')
178
140
  warranty: Optional[str] = None
@@ -180,3 +142,66 @@ class MeliProduct(Product):
180
142
  listing_type_id: Optional[str] = Field(default=None, alias='listingTypeId')
181
143
  catalog_products_status: Optional[str] = Field(default=None, alias='catalogProductsStatus')
182
144
 
145
+
146
+ @dataclass
147
+ class ProductService(BaseService[Product]):
148
+ endpoint: str = Field(default='products', exclude=True)
149
+ create_if_it_doesnt_exist: bool = Field(default=False, exclude=True)
150
+
151
+ def __call__(self, **payload):
152
+ self._record = Product(**payload)
153
+ return self
154
+
155
+ def create(self):
156
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
157
+ headers = self.config.headers.copy()
158
+ headers.update({"createifitdoesntexist": str(self.create_if_it_doesnt_exist).lower()})
159
+ data = self._record.model_dump_json(by_alias=True, exclude_none=True)
160
+ response = requests.post(endpoint_url, data=data, headers=headers)
161
+ if response.status_code == 204:
162
+ raise Exception("Product does not exist and the request cant create if it does not exist")
163
+ return Product(**response.json())
164
+
165
+ def update(self):
166
+ # TODO: Change name to synchronize
167
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
168
+ headers = self.config.headers.copy()
169
+ data = self._record.model_dump_json(by_alias=True, exclude_none=True)
170
+ if not self._record.code and not self._record.sku:
171
+ raise "Sku or code should be provided to update the product"
172
+ response = requests.post(endpoint_url, data=data, headers=headers)
173
+ if response.status_code == 204:
174
+ raise Exception("Product does not exist and the request cant create if it does not exist")
175
+ return Product(**response.json())
176
+
177
+ def get(self, product_id: int) -> "Product":
178
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/{product_id}')
179
+ headers = self.config.headers
180
+ response = requests.get(endpoint_url, headers=headers)
181
+ if not response.ok:
182
+ raise Exception(f"Error getting product {product_id}\n {response.text}")
183
+ response_data = response.json()
184
+ return Product(**response_data)
185
+
186
+ def get_bundle(self, product_id: int) -> "Product":
187
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/{product_id}/bundles')
188
+ headers = self.config.headers
189
+ response = requests.get(endpoint_url, headers=headers)
190
+ if not response.ok:
191
+ raise Exception(f"Error getting bundle {product_id}\n {response.text}")
192
+ return Product(**response.json())
193
+
194
+ def get_ml_integration(self, product_id: int) -> "MeliProduct":
195
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/{product_id}/listingintegration')
196
+ headers = self.config.headers
197
+ response = requests.get(endpoint_url, headers=headers)
198
+ if not response.ok:
199
+ raise Exception(f"Error getting ml integration {product_id}\n {response.text}")
200
+ return MeliProduct(**response.json())
201
+
202
+ def search(self, params: SearchProductParams) -> SearchProduct:
203
+ endpoint: str = f'search/{self.endpoint}'
204
+ headers = self.config.headers
205
+ url = self.config.get_endpoint(endpoint)
206
+ response = requests.get(url, headers=headers, params=params.model_dump(by_alias=True, exclude_none=True))
207
+ return SearchProduct(**response.json())
@@ -1,7 +1,5 @@
1
1
  from typing import List, Optional
2
2
  from pydantic import BaseModel, Field
3
- import requests
4
- from ..config.config import ConfigProducteca
5
3
 
6
4
 
7
5
  class FacetValue(BaseModel):
@@ -124,7 +122,7 @@ class SearchResultItem(BaseModel):
124
122
  channel_pictures_templates_apps: Optional[List[str]]
125
123
 
126
124
 
127
- class SearchProductResponse(BaseModel):
125
+ class SearchProduct(BaseModel):
128
126
  count: int
129
127
  facets: List[Facet]
130
128
  results: List[SearchResultItem]
@@ -138,12 +136,4 @@ class SearchProductParams(BaseModel):
138
136
  sales_channel: Optional[str] = Field(default='2', alias='salesChannel')
139
137
 
140
138
 
141
- class SearchProduct:
142
- endpoint: str = 'search/products'
143
139
 
144
- @classmethod
145
- def search_product(cls, config: ConfigProducteca, params: SearchProductParams) -> SearchProductResponse:
146
- headers = config.headers
147
- url = config.get_endpoint(cls.endpoint)
148
- response = requests.get(url, headers=headers, params=params.model_dump(by_alias=True, exclude_none=True))
149
- return SearchProductResponse(**response.json())
@@ -1,17 +1,17 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, Mock
3
- from producteca.config.config import ConfigProducteca
4
- from producteca.products.products import Product, MeliProduct
3
+ from producteca.products.products import Product
4
+ from producteca.client import ProductecaClient
5
5
 
6
6
 
7
7
  class TestProduct(unittest.TestCase):
8
8
  def setUp(self):
9
- self.config = ConfigProducteca(token="test_id", api_key="test_secret")
9
+ self.client = ProductecaClient(token="test_client_id", api_key="test_client_secret")
10
10
  self.test_product = Product(
11
- config=self.config,
12
11
  sku="TEST001",
13
12
  name="Test Product",
14
- code="TEST001"
13
+ code="TEST001",
14
+ category="Test"
15
15
  )
16
16
 
17
17
  @patch('requests.post')
@@ -19,13 +19,12 @@ class TestProduct(unittest.TestCase):
19
19
  # Mock successful response
20
20
  mock_response = Mock()
21
21
  mock_response.status_code = 200
22
- mock_response.json.return_value = {"id": 1, "sku": "TEST001"}
22
+ mock_response.json.return_value = self.test_product.model_dump()
23
23
  mock_post.return_value = mock_response
24
24
 
25
- response, status_code = self.test_product.create()
25
+ response = self.client.Product(**self.test_product.model_dump()).create()
26
26
 
27
- self.assertEqual(status_code, 200)
28
- self.assertEqual(response["sku"], "TEST001")
27
+ self.assertEqual(response.sku, "TEST001")
29
28
 
30
29
  @patch('requests.post')
31
30
  def test_create_product_not_exist(self, mock_post):
@@ -34,48 +33,45 @@ class TestProduct(unittest.TestCase):
34
33
  mock_response.status_code = 204
35
34
  mock_post.return_value = mock_response
36
35
 
37
- response, status_code = self.test_product.create()
38
-
39
- self.assertEqual(status_code, 204)
40
- self.assertEqual(response["Message"], "Product does not exist and the request cant create if it does not exist")
36
+ with self.assertRaises(Exception):
37
+ self.client.Product.create()
41
38
 
42
39
  @patch('requests.post')
43
40
  def test_update_product_success(self, mock_post):
44
41
  # Mock successful update
45
42
  mock_response = Mock()
46
43
  mock_response.status_code = 200
47
- mock_response.json.return_value = {"id": 1, "sku": "TEST001", "name": "Updated Product"}
44
+ mock_response.json.return_value = self.test_product.model_dump()
48
45
  mock_post.return_value = mock_response
49
46
 
50
- response, status_code = self.test_product.update()
47
+ response = self.client.Product(**self.test_product.model_dump()).update()
51
48
 
52
- self.assertEqual(status_code, 200)
53
- self.assertEqual(response["name"], "Updated Product")
49
+ self.assertEqual(response.name, "Test Product")
54
50
 
55
51
  @patch('requests.get')
56
52
  def test_get_product(self, mock_get):
57
53
  # Mock get product response
58
54
  mock_response = Mock()
59
55
  mock_response.status_code = 200
60
- mock_response.json.return_value = {"id": 1, "sku": "TEST001"}
56
+ mock_response.json.return_value = self.test_product.model_dump()
61
57
  mock_get.return_value = mock_response
62
58
 
63
- response, status_code = Product.get(self.config, 1)
59
+ response = self.client.Product.get(1)
64
60
 
65
- self.assertEqual(status_code, 200)
66
- self.assertEqual(response["sku"], "TEST001")
61
+ self.assertEqual(response.sku, "TEST001")
67
62
 
68
63
  @patch('requests.get')
69
64
  def test_get_bundle(self, mock_get):
70
65
  # Mock get bundle response
71
66
  mock_response = Mock()
72
67
  mock_response.status_code = 200
73
- mock_response.json.return_value = {"sku": "TEST001", "bundles": []}
68
+ test_prod = self.test_product.model_dump()
69
+ test_prod.update({"sku": "TEST001", "bundles": []})
70
+ mock_response.json.return_value = test_prod
74
71
  mock_get.return_value = mock_response
75
72
 
76
- product, status_code = Product.get_bundle(self.config, 1)
73
+ product = self.client.Product.get_bundle(1)
77
74
 
78
- self.assertEqual(status_code, 200)
79
75
  self.assertEqual(product.sku, "TEST001")
80
76
 
81
77
  @patch('requests.get')
@@ -83,13 +79,15 @@ class TestProduct(unittest.TestCase):
83
79
  # Mock ML integration response
84
80
  mock_response = Mock()
85
81
  mock_response.status_code = 200
86
- mock_response.json.return_value = {"sku": "TEST001", "integrations": []}
82
+ test_prod = self.test_product.model_dump()
83
+ test_prod.update({"sku": "TEST001", "integrations": []})
84
+ mock_response.json.return_value = test_prod
87
85
  mock_get.return_value = mock_response
88
86
 
89
- product, status_code = Product.get_ml_integration(self.config, 1)
87
+ product = self.client.Product.get_ml_integration(1)
90
88
 
91
- self.assertEqual(status_code, 200)
92
89
  self.assertEqual(product.sku, "TEST001")
93
90
 
91
+
94
92
  if __name__ == '__main__':
95
93
  unittest.main()
@@ -1,81 +1,12 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, Mock
3
- from producteca.config.config import ConfigProducteca
4
- from producteca.search.search_sale_orders import SearchSalesOrder, SearchSalesOrderParams, SearchSalesOrderResponse
5
- from producteca.search.search import SearchProduct, SearchProductParams
6
-
7
- class TestSearchSalesOrder(unittest.TestCase):
8
- def setUp(self):
9
- self.config = ConfigProducteca(
10
- token="test_client_id",
11
- api_key="test_client_secret",
12
- )
13
- self.params = SearchSalesOrderParams(
14
- top=10,
15
- skip=0,
16
- filter="status eq 'confirmed'"
17
- )
18
-
19
- @patch('requests.get')
20
- def test_search_saleorder_success(self, mock_get):
21
- # Mock successful response
22
- mock_response = Mock()
23
- mock_response.json.return_value = {
24
- "count": 1,
25
- "results": [{
26
- "id": "123",
27
- "status": "confirmed",
28
- "lines": [],
29
- "payments": [],
30
- "shipments": [],
31
- "integrations": [],
32
- "codes": [],
33
- "integration_ids": [],
34
- "product_names": [],
35
- "skus": [],
36
- "tags": [],
37
- "brands": []
38
- }]
39
- }
40
- mock_response.status_code = 200
41
- mock_get.return_value = mock_response
42
-
43
- response, status_code = SearchSalesOrder.search_saleorder(self.config, self.params)
44
-
45
- # Validate response
46
- self.assertEqual(status_code, 200)
47
- self.assertEqual(response["count"], 1)
48
- self.assertEqual(len(response["results"]), 1)
49
- self.assertEqual(response["results"][0]["id"], "123")
50
-
51
- # Verify the request was made with correct parameters
52
- expected_url = f"{self.config.get_endpoint(SearchSalesOrder.endpoint)}?$filter={self.params.filter}&top={self.params.top}&skip={self.params.skip}"
53
- mock_get.assert_called_once_with(
54
- expected_url,
55
- headers=self.config.headers
56
- )
57
-
58
- @patch('requests.get')
59
- def test_search_saleorder_error(self, mock_get):
60
- # Mock error response
61
- mock_response = Mock()
62
- mock_response.json.return_value = {"error": "Invalid request"}
63
- mock_response.status_code = 400
64
- mock_get.return_value = mock_response
65
-
66
- response, status_code = SearchSalesOrder.search_saleorder(self.config, self.params)
67
-
68
- # Validate error response
69
- self.assertEqual(status_code, 400)
70
- self.assertEqual(response["error"], "Invalid request")
3
+ from producteca.products.search_products import SearchProductParams
4
+ from producteca.client import ProductecaClient
71
5
 
72
6
 
73
7
  class TestSearchProduct(unittest.TestCase):
74
8
  def setUp(self):
75
- self.config = ConfigProducteca(
76
- token="test_client_id",
77
- api_key="test_client_secret",
78
- )
9
+ self.client = ProductecaClient(token="test_client_id", api_key="test_client_secret")
79
10
  self.params = SearchProductParams(
80
11
  top=10,
81
12
  skip=0,
@@ -181,15 +112,13 @@ class TestSearchProduct(unittest.TestCase):
181
112
  mock_response.status_code = 200
182
113
  mock_get.return_value = mock_response
183
114
 
184
- response = SearchProduct.search_product(self.config, self.params)
115
+ response = self.client.Product.search(self.params)
185
116
 
186
117
  # Validate response
187
118
  self.assertEqual(response.count, 1)
188
119
  self.assertEqual(len(response.results), 1)
189
120
  self.assertEqual(response.results[0].id, 123)
190
121
  self.assertEqual(response.results[0].name, "Test Product")
191
-
192
-
193
122
 
194
123
  @patch('requests.get')
195
124
  def test_search_product_error(self, mock_get):
@@ -200,7 +129,7 @@ class TestSearchProduct(unittest.TestCase):
200
129
  mock_get.return_value = mock_response
201
130
  # TODO: Fix this
202
131
  # with self.assertRaises(Exception):
203
- # SearchProduct.search_product(self.config, self.params)
132
+ # self.client.Product.search(self.params)
204
133
 
205
134
 
206
135
  if __name__ == '__main__':
@@ -1 +0,0 @@
1
- from . import sales_orders