amazon-orders 2.0.2__tar.gz → 3.0.0__tar.gz
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.
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/CHANGELOG.md +29 -1
- {amazon_orders-2.0.2/amazon_orders.egg-info → amazon_orders-3.0.0}/PKG-INFO +1 -1
- {amazon_orders-2.0.2 → amazon_orders-3.0.0/amazon_orders.egg-info}/PKG-INFO +1 -1
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/__init__.py +1 -1
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/cli.py +29 -20
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/constants.py +16 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/entity/item.py +6 -5
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/entity/order.py +8 -28
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/entity/parsable.py +24 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/forms.py +12 -9
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/selectors.py +30 -15
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/session.py +2 -1
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/util.py +3 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/tests/test_orders.py +110 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/tests/testcase.py +111 -27
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/LICENSE +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/MANIFEST.in +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/README.md +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazon_orders.egg-info/SOURCES.txt +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazon_orders.egg-info/dependency_links.txt +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazon_orders.egg-info/entry_points.txt +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazon_orders.egg-info/requires.txt +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazon_orders.egg-info/top_level.txt +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/banner.txt +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/conf.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/entity/__init__.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/entity/recipient.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/entity/seller.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/entity/shipment.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/exception.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/amazonorders/orders.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/pyproject.toml +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/setup.cfg +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/tests/test_cli.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/tests/test_conf.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/tests/test_session.py +0 -0
- {amazon_orders-2.0.2 → amazon_orders-3.0.0}/tests/test_util.py +0 -0
|
@@ -4,7 +4,35 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/
|
|
7
|
+
## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/3.0.0...HEAD)
|
|
8
|
+
|
|
9
|
+
## [3.0.0](https://github.com/alexdlaird/amazon-orders/compare/2.0.3...3.0.0) - 2024-11-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Retry support to CLI when stale session fails to authenticate the first time.
|
|
14
|
+
- Improvements to exception messages on auth failures.
|
|
15
|
+
- Documentation improvements.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Several parsing issues with the implementation of Amazon's new `data-component` tag.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- `Order.order_shipped_date`, this cannot be consistently parsed from Amazon.
|
|
24
|
+
- `Order.refund_completed_date`, this cannot be consistently parsed from Amazon.
|
|
25
|
+
|
|
26
|
+
## [2.0.3](https://github.com/alexdlaird/amazon-orders/compare/2.0.2...2.0.3) - 2024-11-01
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Further support for Amazon's new `data-component` tag on order price, seller, and return eligibility, and fixing an issue with `Shipment` parsing.
|
|
31
|
+
- [`Parsable.to_date()`](https://amazon-orders.readthedocs.io/api.html#amazonorders.entity.parsable.Parsable.to_date) attempts multiple date formats.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- An issue with `Shipment`s parsing with Amazon's new `data-component`.
|
|
8
36
|
|
|
9
37
|
## [2.0.2](https://github.com/alexdlaird/amazon-orders/compare/2.0.1...2.0.2) - 2024-10-30
|
|
10
38
|
|
|
@@ -15,7 +15,7 @@ from click.core import Context
|
|
|
15
15
|
from amazonorders import __version__, util
|
|
16
16
|
from amazonorders.conf import AmazonOrdersConfig
|
|
17
17
|
from amazonorders.entity.order import Order
|
|
18
|
-
from amazonorders.exception import AmazonOrdersError
|
|
18
|
+
from amazonorders.exception import AmazonOrdersError, AmazonOrdersAuthError
|
|
19
19
|
from amazonorders.orders import AmazonOrders
|
|
20
20
|
from amazonorders.session import AmazonSession, IODefault
|
|
21
21
|
|
|
@@ -251,21 +251,34 @@ def _print_banner() -> None:
|
|
|
251
251
|
click.echo(banner.format(version=__version__))
|
|
252
252
|
|
|
253
253
|
|
|
254
|
-
def _authenticate(amazon_session: AmazonSession
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
254
|
+
def _authenticate(amazon_session: AmazonSession,
|
|
255
|
+
retries: int = 0) -> None:
|
|
256
|
+
try:
|
|
257
|
+
if amazon_session.auth_cookies_stored():
|
|
258
|
+
if amazon_session.username or amazon_session.password:
|
|
259
|
+
click.echo(
|
|
260
|
+
"Info: The --username and --password flags are ignored because a previous session "
|
|
261
|
+
"still exists. If you would like to reauthenticate, call the `logout` command "
|
|
262
|
+
"first.\n")
|
|
263
|
+
else:
|
|
264
|
+
if not amazon_session.username:
|
|
265
|
+
amazon_session.username = click.prompt("Username")
|
|
266
|
+
if not amazon_session.password:
|
|
267
|
+
amazon_session.password = click.prompt("Password", hide_input=True)
|
|
268
|
+
click.echo("")
|
|
269
|
+
|
|
270
|
+
amazon_session.login()
|
|
271
|
+
except AmazonOrdersAuthError as e:
|
|
272
|
+
if retries < 1:
|
|
273
|
+
if amazon_session.username:
|
|
274
|
+
click.echo(
|
|
275
|
+
f"Info: Authenticating '{amazon_session.username}' failed, retrying ...\n")
|
|
276
|
+
|
|
277
|
+
amazon_session.password = None
|
|
278
|
+
|
|
279
|
+
_authenticate(amazon_session, retries=retries + 1)
|
|
280
|
+
else:
|
|
281
|
+
raise e
|
|
269
282
|
|
|
270
283
|
|
|
271
284
|
def _order_output(order: Order) -> str:
|
|
@@ -299,10 +312,6 @@ Order #{}
|
|
|
299
312
|
order_str += f"\n Estimated Tax: ${order.estimated_tax:,.2f}"
|
|
300
313
|
if order.refund_total:
|
|
301
314
|
order_str += f"\n Refund Total: ${order.refund_total:,.2f}"
|
|
302
|
-
if order.order_shipped_date:
|
|
303
|
-
order_str += f"\n Order Shipped Date: {order.order_shipped_date}"
|
|
304
|
-
if order.refund_completed_date:
|
|
305
|
-
order_str += f"\n Refund Completed Date: {order.refund_completed_date}"
|
|
306
315
|
|
|
307
316
|
order_str += "\n-----------------------------------------------------------------------"
|
|
308
317
|
|
|
@@ -5,6 +5,16 @@ import os
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Constants:
|
|
8
|
+
"""
|
|
9
|
+
A class containing useful constants. Extend and override with `constants_class` in the config:
|
|
10
|
+
|
|
11
|
+
.. code-block:: python
|
|
12
|
+
|
|
13
|
+
from amazonorders.conf import AmazonOrdersConfig
|
|
14
|
+
|
|
15
|
+
config = AmazonOrdersConfig(data={"constants_class": "my_module.MyConstants"})
|
|
16
|
+
"""
|
|
17
|
+
|
|
8
18
|
##########################################################################
|
|
9
19
|
# General URL
|
|
10
20
|
##########################################################################
|
|
@@ -53,3 +63,9 @@ class Constants:
|
|
|
53
63
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
54
64
|
"Chrome/120.0.0.0 Safari/537.36",
|
|
55
65
|
}
|
|
66
|
+
|
|
67
|
+
##########################################################################
|
|
68
|
+
# Formats
|
|
69
|
+
##########################################################################
|
|
70
|
+
|
|
71
|
+
VALID_DATE_FORMATS = ["%b %d, %Y", "%B %d, %Y"]
|
|
@@ -2,7 +2,7 @@ __copyright__ = "Copyright (c) 2024 Alex Laird"
|
|
|
2
2
|
__license__ = "MIT"
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
-
from datetime import date
|
|
5
|
+
from datetime import date
|
|
6
6
|
from typing import Optional, TypeVar
|
|
7
7
|
|
|
8
8
|
from bs4 import Tag
|
|
@@ -35,11 +35,11 @@ class Item(Parsable):
|
|
|
35
35
|
link=True, required=True)
|
|
36
36
|
#: The Item price.
|
|
37
37
|
self.price: Optional[float] = self.to_currency(
|
|
38
|
-
self.safe_simple_parse(selector=self.config.selectors.
|
|
38
|
+
self.safe_simple_parse(selector=self.config.selectors.FIELD_ITEM_PRICE_SELECTOR,
|
|
39
39
|
prefix_split="$"))
|
|
40
40
|
#: The Item Seller.
|
|
41
41
|
self.seller: Optional[Seller] = self.safe_simple_parse(
|
|
42
|
-
selector=self.config.selectors.
|
|
42
|
+
selector=self.config.selectors.FIELD_ITEM_SELLER_SELECTOR,
|
|
43
43
|
text_contains="Sold by:",
|
|
44
44
|
wrap_tag=Seller)
|
|
45
45
|
#: The Item condition.
|
|
@@ -69,7 +69,7 @@ class Item(Parsable):
|
|
|
69
69
|
def _parse_return_eligible_date(self) -> Optional[date]:
|
|
70
70
|
value = None
|
|
71
71
|
|
|
72
|
-
for tag in util.select(self.parsed, self.config.selectors.
|
|
72
|
+
for tag in util.select(self.parsed, self.config.selectors.FIELD_ITEM_RETURN_SELECTOR):
|
|
73
73
|
if "Return" in tag.text:
|
|
74
74
|
tag_str = tag.text.strip()
|
|
75
75
|
split_str = "through "
|
|
@@ -77,6 +77,7 @@ class Item(Parsable):
|
|
|
77
77
|
split_str = "closed on "
|
|
78
78
|
if split_str in tag_str:
|
|
79
79
|
date_str = tag_str.split(split_str)[1]
|
|
80
|
-
value =
|
|
80
|
+
value = self.to_date(date_str)
|
|
81
|
+
break
|
|
81
82
|
|
|
82
83
|
return value
|
|
@@ -3,7 +3,7 @@ __license__ = "MIT"
|
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
-
from datetime import date
|
|
6
|
+
from datetime import date
|
|
7
7
|
from typing import Any, List, Optional, TypeVar, Union
|
|
8
8
|
|
|
9
9
|
from bs4 import BeautifulSoup, Tag
|
|
@@ -72,10 +72,6 @@ class Order(Parsable):
|
|
|
72
72
|
self.estimated_tax: Optional[float] = self._if_full_details(self._parse_estimated_tax())
|
|
73
73
|
#: The Order refund total. Only populated when ``full_details`` is ``True``.
|
|
74
74
|
self.refund_total: Optional[float] = self._if_full_details(self._parse_refund_total())
|
|
75
|
-
#: The Order shipped date. Only populated when ``full_details`` is ``True``.
|
|
76
|
-
self.order_shipped_date: Optional[date] = self._if_full_details(self._parse_order_shipping_date())
|
|
77
|
-
#: The Order refund total. Only populated when ``full_details`` is ``True``.
|
|
78
|
-
self.refund_completed_date: Optional[date] = self._if_full_details(self._parse_refund_completed_date())
|
|
79
75
|
|
|
80
76
|
def __repr__(self) -> str:
|
|
81
77
|
return f"<Order #{self.order_number}: \"{self.items}\">"
|
|
@@ -137,19 +133,23 @@ class Order(Parsable):
|
|
|
137
133
|
else:
|
|
138
134
|
split_str = "Order placed"
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
value =
|
|
136
|
+
date_str = value.split(split_str)[1].strip()
|
|
137
|
+
value = self.to_date(date_str)
|
|
142
138
|
|
|
143
139
|
return value
|
|
144
140
|
|
|
145
141
|
def _parse_recipient(self) -> Optional[Recipient]:
|
|
142
|
+
# At least for now, we don't populate Recipient data for digital orders
|
|
143
|
+
if util.select_one(self.parsed, self.config.selectors.FIELD_ORDER_GIFT_CARD_INSTANCE_SELECTOR):
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
146
|
value = util.select_one(self.parsed, self.config.selectors.FIELD_ORDER_ADDRESS_SELECTOR)
|
|
147
147
|
|
|
148
148
|
if not value:
|
|
149
149
|
value = util.select_one(self.parsed, self.config.selectors.FIELD_ORDER_ADDRESS_FALLBACK_1_SELECTOR)
|
|
150
150
|
|
|
151
151
|
if value:
|
|
152
|
-
data_popover = value.get("data-a-popover", {}) # type: ignore[arg-type]
|
|
152
|
+
data_popover = value.get("data-a-popover", {}) # type: ignore[arg-type, var-annotated]
|
|
153
153
|
inline_content = data_popover.get("inlineContent") # type: ignore[union-attr]
|
|
154
154
|
if inline_content:
|
|
155
155
|
value = BeautifulSoup(json.loads(inline_content), "html.parser")
|
|
@@ -266,26 +266,6 @@ class Order(Parsable):
|
|
|
266
266
|
|
|
267
267
|
return value
|
|
268
268
|
|
|
269
|
-
def _parse_order_shipping_date(self) -> Optional[date]:
|
|
270
|
-
value = self.simple_parse(self.config.selectors.FIELD_ORDER_SHIPPED_DATE_SELECTOR,
|
|
271
|
-
prefix_split="Items shipped:")
|
|
272
|
-
|
|
273
|
-
if value:
|
|
274
|
-
date_str = value.split("-")[0].strip()
|
|
275
|
-
value = datetime.strptime(date_str, "%B %d, %Y").date()
|
|
276
|
-
|
|
277
|
-
return value
|
|
278
|
-
|
|
279
|
-
def _parse_refund_completed_date(self) -> Optional[date]:
|
|
280
|
-
value = self.simple_parse(self.config.selectors.FIELD_ORDER_REFUND_COMPLETED_DATE,
|
|
281
|
-
prefix_split="Refund: Completed")
|
|
282
|
-
|
|
283
|
-
if value:
|
|
284
|
-
date_str = value.split("-")[0].strip()
|
|
285
|
-
value = datetime.strptime(date_str, "%B %d, %Y").date()
|
|
286
|
-
|
|
287
|
-
return value
|
|
288
|
-
|
|
289
269
|
def _if_full_details(self,
|
|
290
270
|
value: Any) -> Union[Any, None]:
|
|
291
271
|
return value if self.full_details else None
|
|
@@ -2,6 +2,7 @@ __copyright__ = "Copyright (c) 2024 Alex Laird"
|
|
|
2
2
|
__license__ = "MIT"
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
+
from datetime import date, datetime
|
|
5
6
|
from typing import Any, Callable, Optional, Type, Union, Dict
|
|
6
7
|
|
|
7
8
|
from bs4 import Tag
|
|
@@ -169,3 +170,26 @@ class Parsable:
|
|
|
169
170
|
return None
|
|
170
171
|
|
|
171
172
|
return currency
|
|
173
|
+
|
|
174
|
+
def to_date(self,
|
|
175
|
+
date_str: str) -> Optional[date]:
|
|
176
|
+
"""
|
|
177
|
+
Return the given date string as a date object.
|
|
178
|
+
|
|
179
|
+
:param date_str: The date string to parse to a date object.
|
|
180
|
+
:return: The parsed date.
|
|
181
|
+
"""
|
|
182
|
+
value = None
|
|
183
|
+
|
|
184
|
+
for fmt in self.config.constants.VALID_DATE_FORMATS:
|
|
185
|
+
try:
|
|
186
|
+
value = datetime.strptime(date_str, fmt).date()
|
|
187
|
+
except ValueError:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
if value is None:
|
|
191
|
+
logger.warning(
|
|
192
|
+
f"ValueError: time data '{date_str}' does not match any format in "
|
|
193
|
+
f"{self.config.constants.VALID_DATE_FORMATS}")
|
|
194
|
+
|
|
195
|
+
return value
|
|
@@ -196,7 +196,8 @@ class SignInForm(AuthForm):
|
|
|
196
196
|
super().fill_form()
|
|
197
197
|
if not self.data:
|
|
198
198
|
raise AmazonOrdersError(
|
|
199
|
-
"
|
|
199
|
+
"SignInForm data did not populate, but it's required. "
|
|
200
|
+
"Check if Amazon changed their login flow."
|
|
200
201
|
) # pragma: no cover
|
|
201
202
|
|
|
202
203
|
additional_attrs.update({self.solution_attr_key: self.amazon_session.username,
|
|
@@ -236,7 +237,8 @@ class MfaDeviceSelectForm(AuthForm):
|
|
|
236
237
|
super().fill_form()
|
|
237
238
|
if not self.data:
|
|
238
239
|
raise AmazonOrdersError(
|
|
239
|
-
"
|
|
240
|
+
"MfaDeviceSelectForm data did not populate, but it's required. "
|
|
241
|
+
"Check if Amazon changed their MFA flow."
|
|
240
242
|
) # pragma: no cover
|
|
241
243
|
|
|
242
244
|
contexts = util.select(self.form, self.config.selectors.MFA_DEVICE_SELECT_INPUT_SELECTOR)
|
|
@@ -281,8 +283,8 @@ class MfaForm(AuthForm):
|
|
|
281
283
|
super().fill_form()
|
|
282
284
|
if not self.data:
|
|
283
285
|
raise AmazonOrdersError(
|
|
284
|
-
"
|
|
285
|
-
"Check if Amazon changed
|
|
286
|
+
"MfaForm data did not populate, but it's required. "
|
|
287
|
+
"Check if Amazon changed their MFA flow."
|
|
286
288
|
) # pragma: no cover
|
|
287
289
|
|
|
288
290
|
otp = self.amazon_session.io.prompt("Enter the one-time passcode sent to your device")
|
|
@@ -320,22 +322,23 @@ class CaptchaForm(AuthForm):
|
|
|
320
322
|
super().fill_form(additional_attrs)
|
|
321
323
|
if not self.data:
|
|
322
324
|
raise AmazonOrdersError(
|
|
323
|
-
"
|
|
325
|
+
"CaptchaForm data did not populate, but it's required. "
|
|
326
|
+
"Check if Amazon changed their Captcha flow."
|
|
324
327
|
) # pragma: no cover
|
|
325
328
|
|
|
326
329
|
# TODO: eliminate the use of find_parent() here
|
|
327
330
|
form_parent = self.form.find_parent()
|
|
328
331
|
if not form_parent:
|
|
329
332
|
raise AmazonOrdersError(
|
|
330
|
-
"
|
|
331
|
-
"Check if Amazon changed
|
|
333
|
+
"CaptchaForm parent not found, but it's required. "
|
|
334
|
+
"Check if Amazon changed their Captcha flow."
|
|
332
335
|
) # pragma: no cover
|
|
333
336
|
|
|
334
337
|
img_tag = form_parent.select_one("img")
|
|
335
338
|
if not img_tag:
|
|
336
339
|
raise AmazonOrdersError(
|
|
337
|
-
"
|
|
338
|
-
"Check if Amazon changed
|
|
340
|
+
"CaptchaForm <img> tag not found, but it's required. "
|
|
341
|
+
"Check if Amazon changed their Captcha flow."
|
|
339
342
|
) # pragma: no cover
|
|
340
343
|
|
|
341
344
|
img_url = str(img_tag["src"])
|
|
@@ -3,6 +3,16 @@ __license__ = "MIT"
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Selectors:
|
|
6
|
+
"""
|
|
7
|
+
A class containing CSS selectors. Extend and override with `selectors_class` in the config:
|
|
8
|
+
|
|
9
|
+
.. code-block:: python
|
|
10
|
+
|
|
11
|
+
from amazonorders.conf import AmazonOrdersConfig
|
|
12
|
+
|
|
13
|
+
config = AmazonOrdersConfig(data={"selectors_class": "my_module.MyConstants"})
|
|
14
|
+
"""
|
|
15
|
+
|
|
6
16
|
##########################################################################
|
|
7
17
|
# CSS selectors for AuthForms
|
|
8
18
|
##########################################################################
|
|
@@ -35,47 +45,52 @@ class Selectors:
|
|
|
35
45
|
# is passed.
|
|
36
46
|
##########################################################################
|
|
37
47
|
|
|
38
|
-
ORDER_HISTORY_ENTITY_SELECTOR = ["div.order", "div.order
|
|
39
|
-
ORDER_DETAILS_ENTITY_SELECTOR = ["div#orderDetails", "div#ordersContainer"
|
|
48
|
+
ORDER_HISTORY_ENTITY_SELECTOR = ["div.order-card", "div.order"]
|
|
49
|
+
ORDER_DETAILS_ENTITY_SELECTOR = ["div#orderDetails", "div#ordersContainer"]
|
|
40
50
|
ITEM_ENTITY_SELECTOR = ["div:has(> div.yohtmlc-item)", ".item-box", "[data-component='purchasedItems']"]
|
|
41
|
-
SHIPMENT_ENTITY_SELECTOR = ["div.shipment", "div.delivery-box",
|
|
51
|
+
SHIPMENT_ENTITY_SELECTOR = ["div.shipment", "div.delivery-box",
|
|
52
|
+
"[data-component='orderCard'] [data-component='shipments']"]
|
|
42
53
|
|
|
43
54
|
#####################################
|
|
44
55
|
# CSS selectors for Item fields
|
|
45
56
|
#####################################
|
|
46
57
|
|
|
47
58
|
FIELD_ITEM_IMG_LINK_SELECTOR = "a img"
|
|
48
|
-
FIELD_ITEM_QUANTITY_SELECTOR = ["
|
|
49
|
-
FIELD_ITEM_TITLE_SELECTOR = [".yohtmlc-item a", ".yohtmlc-product-title"
|
|
50
|
-
FIELD_ITEM_LINK_SELECTOR = [".yohtmlc-item a", "a:has(> .yohtmlc-product-title)"
|
|
51
|
-
FIELD_ITEM_TAG_ITERATOR_SELECTOR = [".yohtmlc-item div"
|
|
59
|
+
FIELD_ITEM_QUANTITY_SELECTOR = [".od-item-view-qty", "span.item-view-qty", "span.product-image__qty"]
|
|
60
|
+
FIELD_ITEM_TITLE_SELECTOR = ["[data-component='itemTitle']", ".yohtmlc-item a", ".yohtmlc-product-title"]
|
|
61
|
+
FIELD_ITEM_LINK_SELECTOR = ["[data-component='itemTitle'] a", ".yohtmlc-item a", "a:has(> .yohtmlc-product-title)"]
|
|
62
|
+
FIELD_ITEM_TAG_ITERATOR_SELECTOR = [".yohtmlc-item div"]
|
|
63
|
+
FIELD_ITEM_PRICE_SELECTOR = ["[data-component='unitPrice']"] + FIELD_ITEM_TAG_ITERATOR_SELECTOR
|
|
64
|
+
FIELD_ITEM_SELLER_SELECTOR = ["[data-component='orderedMerchant']"] + FIELD_ITEM_TAG_ITERATOR_SELECTOR
|
|
65
|
+
FIELD_ITEM_RETURN_SELECTOR = ["[data-component='itemReturnEligibility']"] + FIELD_ITEM_TAG_ITERATOR_SELECTOR
|
|
52
66
|
|
|
53
67
|
#####################################
|
|
54
68
|
# CSS selectors for Order fields
|
|
55
69
|
#####################################
|
|
56
70
|
|
|
57
71
|
FIELD_ORDER_DETAILS_LINK_SELECTOR = "a.yohtmlc-order-details-link"
|
|
58
|
-
FIELD_ORDER_NUMBER_SELECTOR = ["bdi[dir='ltr']", "span[dir='ltr']"]
|
|
72
|
+
FIELD_ORDER_NUMBER_SELECTOR = [".order-date-invoice-item bdi[dir='ltr']", "bdi[dir='ltr']", "span[dir='ltr']"]
|
|
59
73
|
FIELD_ORDER_GRAND_TOTAL_SELECTOR = ["div.yohtmlc-order-total span.value", "div.order-header div.a-column.a-span2"]
|
|
60
|
-
FIELD_ORDER_PLACED_DATE_SELECTOR = ["span.order-date-invoice-item",
|
|
61
|
-
"div.a-span3"]
|
|
74
|
+
FIELD_ORDER_PLACED_DATE_SELECTOR = ["span.order-date-invoice-item", "div.a-span3"]
|
|
62
75
|
FIELD_ORDER_PAYMENT_METHOD_SELECTOR = "img.pmts-payment-credit-card-instrument-logo"
|
|
63
76
|
FIELD_ORDER_PAYMENT_METHOD_LAST_4_SELECTOR = "img.pmts-payment-credit-card-instrument-logo"
|
|
64
|
-
FIELD_ORDER_SUBTOTALS_TAG_ITERATOR_SELECTOR = ["
|
|
77
|
+
FIELD_ORDER_SUBTOTALS_TAG_ITERATOR_SELECTOR = ["[data-component='orderSubtotals'] div.a-row",
|
|
78
|
+
"div#od-subtotals div.a-row"]
|
|
65
79
|
FIELD_ORDER_SUBTOTALS_INNER_TAG_SELECTOR = "div.a-span-last"
|
|
66
80
|
FIELD_ORDER_ADDRESS_SELECTOR = "div.displayAddressDiv"
|
|
67
81
|
FIELD_ORDER_ADDRESS_FALLBACK_1_SELECTOR = "div.recipient span.a-declarative"
|
|
68
82
|
FIELD_ORDER_ADDRESS_FALLBACK_2_SELECTOR = "script[id^='shipToData']"
|
|
69
|
-
|
|
70
|
-
FIELD_ORDER_REFUND_COMPLETED_DATE = "#orderDetails div.a-box.a-last div div div.a-row.a-color-success"
|
|
83
|
+
FIELD_ORDER_GIFT_CARD_INSTANCE_SELECTOR = ".gift-card-instance"
|
|
71
84
|
|
|
72
85
|
#####################################
|
|
73
86
|
# CSS selectors for Shipment fields
|
|
74
87
|
#####################################
|
|
75
88
|
|
|
76
|
-
FIELD_SHIPMENT_TRACKING_LINK_SELECTOR = "span.track-package-button a"
|
|
89
|
+
FIELD_SHIPMENT_TRACKING_LINK_SELECTOR = ["span.track-package-button a", "a[href*='ship-track?itemId=']"]
|
|
77
90
|
FIELD_SHIPMENT_DELIVERY_STATUS_SELECTOR = ["div.js-shipment-info-container div.a-row",
|
|
78
|
-
"span.delivery-box__primary-text"
|
|
91
|
+
"span.delivery-box__primary-text",
|
|
92
|
+
".yohtmlc-shipment-status-primaryText",
|
|
93
|
+
".od-status-message"]
|
|
79
94
|
|
|
80
95
|
#####################################
|
|
81
96
|
# CSS selectors for Recipient fields
|
|
@@ -98,7 +98,8 @@ class AmazonSession:
|
|
|
98
98
|
self.io: IODefault = io
|
|
99
99
|
#: The AmazonOrdersConfig to use.
|
|
100
100
|
self.config: AmazonOrdersConfig = config
|
|
101
|
-
#: The list of
|
|
101
|
+
#: The list of form implementations to use with authentication. If a value is passed for this when
|
|
102
|
+
#: instantiating an AmazonSession, ensure that list is populated with the default form implementations.
|
|
102
103
|
self.auth_forms: List[AuthForm] = auth_forms
|
|
103
104
|
|
|
104
105
|
#: The shared session to be used across all requests.
|
|
@@ -2,10 +2,13 @@ __copyright__ = "Copyright (c) 2024 Alex Laird"
|
|
|
2
2
|
__license__ = "MIT"
|
|
3
3
|
|
|
4
4
|
import importlib
|
|
5
|
+
import logging
|
|
5
6
|
from typing import List, Union, Optional, Callable
|
|
6
7
|
|
|
7
8
|
from bs4 import Tag
|
|
8
9
|
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
def select(parsed: Tag, selector: Union[List[str], str]) -> List[Tag]:
|
|
11
14
|
"""
|
|
@@ -51,6 +51,32 @@ class TestOrders(UnitTestCase):
|
|
|
51
51
|
self.assertEqual(1, resp1.call_count)
|
|
52
52
|
self.assertEqual(1, resp2.call_count)
|
|
53
53
|
|
|
54
|
+
@responses.activate
|
|
55
|
+
def test_get_order_history_2024_data_component(self):
|
|
56
|
+
# GIVEN
|
|
57
|
+
self.amazon_session.is_authenticated = True
|
|
58
|
+
year = 2024
|
|
59
|
+
start_index = 0
|
|
60
|
+
resp1 = self.given_order_history_landing_exists()
|
|
61
|
+
resp2 = self.given_order_history_exists(year, start_index)
|
|
62
|
+
|
|
63
|
+
# WHEN
|
|
64
|
+
orders = self.amazon_orders.get_order_history(year=year, start_index=start_index)
|
|
65
|
+
|
|
66
|
+
# THEN
|
|
67
|
+
# Giving start_index=0 means we only got the first page, so just 10 results
|
|
68
|
+
self.assertEqual(10, len(orders))
|
|
69
|
+
# Regular order with new `data-component` fields
|
|
70
|
+
self.assert_order_112_5939971_8962610_data_component(orders[0], False)
|
|
71
|
+
# Gift card order
|
|
72
|
+
self.assert_order_112_4482432_2955442_gift_card(orders[2], False)
|
|
73
|
+
# Digital order (legacy)
|
|
74
|
+
self.assert_order_112_9087159_1657009_digital_order_legacy(orders[3], False)
|
|
75
|
+
# Subscription order
|
|
76
|
+
self.assert_order_114_8722141_6545058_data_component_subscription(orders[6], False)
|
|
77
|
+
self.assertEqual(1, resp1.call_count)
|
|
78
|
+
self.assertEqual(1, resp2.call_count)
|
|
79
|
+
|
|
54
80
|
@responses.activate
|
|
55
81
|
def test_get_order_history_paginated(self):
|
|
56
82
|
# GIVEN
|
|
@@ -198,6 +224,90 @@ class TestOrders(UnitTestCase):
|
|
|
198
224
|
self.assert_order_112_9685975_5907428_multiple_items_shipments_sellers(order, True)
|
|
199
225
|
self.assertEqual(1, resp1.call_count)
|
|
200
226
|
|
|
227
|
+
@responses.activate
|
|
228
|
+
def test_get_order_2024_data_component(self):
|
|
229
|
+
# GIVEN
|
|
230
|
+
self.amazon_session.is_authenticated = True
|
|
231
|
+
order_id = "112-5939971-8962610"
|
|
232
|
+
with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
|
|
233
|
+
encoding="utf-8") as f:
|
|
234
|
+
resp1 = responses.add(
|
|
235
|
+
responses.GET,
|
|
236
|
+
f"{self.test_config.constants.ORDER_DETAILS_URL}?orderID={order_id}",
|
|
237
|
+
body=f.read(),
|
|
238
|
+
status=200,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# WHEN
|
|
242
|
+
order = self.amazon_orders.get_order(order_id)
|
|
243
|
+
|
|
244
|
+
# THEN
|
|
245
|
+
self.assert_order_112_5939971_8962610_data_component(order, True)
|
|
246
|
+
self.assertEqual(1, resp1.call_count)
|
|
247
|
+
|
|
248
|
+
@responses.activate
|
|
249
|
+
def test_get_order_2024_gift_card(self):
|
|
250
|
+
# GIVEN
|
|
251
|
+
self.amazon_session.is_authenticated = True
|
|
252
|
+
order_id = "112-4482432-2955442"
|
|
253
|
+
with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
|
|
254
|
+
encoding="utf-8") as f:
|
|
255
|
+
resp1 = responses.add(
|
|
256
|
+
responses.GET,
|
|
257
|
+
f"{self.test_config.constants.ORDER_DETAILS_URL}?orderID={order_id}",
|
|
258
|
+
body=f.read(),
|
|
259
|
+
status=200,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# WHEN
|
|
263
|
+
order = self.amazon_orders.get_order(order_id)
|
|
264
|
+
|
|
265
|
+
# THEN
|
|
266
|
+
self.assert_order_112_4482432_2955442_gift_card(order, True)
|
|
267
|
+
self.assertEqual(1, resp1.call_count)
|
|
268
|
+
|
|
269
|
+
@responses.activate
|
|
270
|
+
def test_get_order_2024_digital_order_legacy(self):
|
|
271
|
+
# GIVEN
|
|
272
|
+
self.amazon_session.is_authenticated = True
|
|
273
|
+
order_id = "112-9087159-1657009"
|
|
274
|
+
with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
|
|
275
|
+
encoding="utf-8") as f:
|
|
276
|
+
resp1 = responses.add(
|
|
277
|
+
responses.GET,
|
|
278
|
+
f"{self.test_config.constants.ORDER_DETAILS_URL}?orderID={order_id}",
|
|
279
|
+
body=f.read(),
|
|
280
|
+
status=200,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# WHEN
|
|
284
|
+
order = self.amazon_orders.get_order(order_id)
|
|
285
|
+
|
|
286
|
+
# THEN
|
|
287
|
+
self.assert_order_112_9087159_1657009_digital_order_legacy(order, True)
|
|
288
|
+
self.assertEqual(1, resp1.call_count)
|
|
289
|
+
|
|
290
|
+
@responses.activate
|
|
291
|
+
def test_get_order_2024_data_component_subscription(self):
|
|
292
|
+
# GIVEN
|
|
293
|
+
self.amazon_session.is_authenticated = True
|
|
294
|
+
order_id = "114-8722141-6545058"
|
|
295
|
+
with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
|
|
296
|
+
encoding="utf-8") as f:
|
|
297
|
+
resp1 = responses.add(
|
|
298
|
+
responses.GET,
|
|
299
|
+
f"{self.test_config.constants.ORDER_DETAILS_URL}?orderID={order_id}",
|
|
300
|
+
body=f.read(),
|
|
301
|
+
status=200,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# WHEN
|
|
305
|
+
order = self.amazon_orders.get_order(order_id)
|
|
306
|
+
|
|
307
|
+
# THEN
|
|
308
|
+
self.assert_order_114_8722141_6545058_data_component_subscription(order, True)
|
|
309
|
+
self.assertEqual(1, resp1.call_count)
|
|
310
|
+
|
|
201
311
|
@unittest.skipIf(not os.path.exists(temp_order_history_file_path),
|
|
202
312
|
reason="Skipped, to debug an order history page, "
|
|
203
313
|
"place it at tests/output/temp-order-history.html")
|
|
@@ -36,9 +36,6 @@ class TestCase(unittest.TestCase):
|
|
|
36
36
|
self.assertIsNone(order.subscription_discount)
|
|
37
37
|
self.assertEqual(30.99, order.total_before_tax)
|
|
38
38
|
self.assertEqual(3.02, order.estimated_tax)
|
|
39
|
-
self.assertEqual(date(2018, 12, 28), order.order_shipped_date)
|
|
40
|
-
# As of April 2024, this is no longer shown in Order History
|
|
41
|
-
# self.assertEqual("New", order.items[0].condition)
|
|
42
39
|
self.assertEqual(30.99, order.items[0].price)
|
|
43
40
|
self.assertEqual("Amazon.com Services, Inc",
|
|
44
41
|
order.items[0].seller.name)
|
|
@@ -73,9 +70,6 @@ class TestCase(unittest.TestCase):
|
|
|
73
70
|
self.assertEqual(-5.83, order.subscription_discount)
|
|
74
71
|
self.assertEqual(33.01, order.total_before_tax)
|
|
75
72
|
self.assertEqual(2.89, order.estimated_tax)
|
|
76
|
-
self.assertEqual(date(2020, 10, 28), order.order_shipped_date)
|
|
77
|
-
# As of April 2024, this is no longer shown in Order History
|
|
78
|
-
# self.assertEqual("New", order.items[0].condition)
|
|
79
73
|
self.assertEqual(38.84, order.items[0].price)
|
|
80
74
|
self.assertEqual("Amazon.com Services, Inc",
|
|
81
75
|
order.items[0].seller.name)
|
|
@@ -111,10 +105,6 @@ class TestCase(unittest.TestCase):
|
|
|
111
105
|
self.assertEqual(69.99, order.total_before_tax)
|
|
112
106
|
self.assertEqual(6.12, order.estimated_tax)
|
|
113
107
|
self.assertEqual(76.11, order.refund_total)
|
|
114
|
-
self.assertEqual(date(2020, 10, 19), order.order_shipped_date)
|
|
115
|
-
self.assertTrue(date(2020, 11, 2), order.refund_completed_date)
|
|
116
|
-
# As of April 2024, this is no longer shown in Order History
|
|
117
|
-
# self.assertEqual("New", order.items[0].condition)
|
|
118
108
|
self.assertEqual(69.99, order.items[0].price)
|
|
119
109
|
self.assertEqual("Amazon.com Services, Inc",
|
|
120
110
|
order.items[0].seller.name)
|
|
@@ -184,12 +174,7 @@ class TestCase(unittest.TestCase):
|
|
|
184
174
|
self.assertIsNone(order.subscription_discount)
|
|
185
175
|
self.assertEqual(43.23, order.total_before_tax)
|
|
186
176
|
self.assertEqual(3.38, order.estimated_tax)
|
|
187
|
-
self.assertEqual(date(2023, 12, 7), order.order_shipped_date)
|
|
188
|
-
# As of April 2024, this is no longer shown in Order History
|
|
189
|
-
# self.assertEqual("New", order.items[0].condition)
|
|
190
177
|
self.assertEqual(14.99, order.items[0].price)
|
|
191
|
-
# As of April 2024, this is no longer shown in Order History
|
|
192
|
-
# self.assertEqual("New", order.items[1].condition)
|
|
193
178
|
self.assertEqual(28.24, order.items[1].price)
|
|
194
179
|
found_cadeya = False
|
|
195
180
|
found_amazon = False
|
|
@@ -266,12 +251,7 @@ class TestCase(unittest.TestCase):
|
|
|
266
251
|
self.assertIsNone(order.subscription_discount)
|
|
267
252
|
self.assertEqual(42.29, order.total_before_tax)
|
|
268
253
|
self.assertEqual(2.96, order.estimated_tax)
|
|
269
|
-
self.assertEqual(date(2024, 5, 16), order.order_shipped_date)
|
|
270
|
-
# As of April 2024, this is no longer shown in Order History
|
|
271
|
-
# self.assertEqual("New", order.items[0].condition)
|
|
272
254
|
self.assertEqual(12.30, order.items[0].price)
|
|
273
|
-
# As of April 2024, this is no longer shown in Order History
|
|
274
|
-
# self.assertEqual("New", order.items[1].condition)
|
|
275
255
|
self.assertEqual(29.99, order.items[1].price)
|
|
276
256
|
found_kimoe = False
|
|
277
257
|
found_amazon = False
|
|
@@ -330,22 +310,17 @@ class TestCase(unittest.TestCase):
|
|
|
330
310
|
self.assertIsNone(order.subscription_discount)
|
|
331
311
|
self.assertEqual(26.48, order.total_before_tax)
|
|
332
312
|
self.assertEqual(2.32, order.estimated_tax)
|
|
333
|
-
self.assertEqual(date(2020, 10, 27), order.order_shipped_date)
|
|
334
313
|
found_aa = False
|
|
335
314
|
found_aaa = False
|
|
336
315
|
for item in order.items:
|
|
337
316
|
if "AAA" in item.title:
|
|
338
317
|
found_aa = True
|
|
339
|
-
# As of April 2024, this is no longer shown in Order History
|
|
340
|
-
# self.assertEqual("New", item.condition)
|
|
341
318
|
self.assertEqual(10.99, item.price)
|
|
342
319
|
self.assertEqual("Amazon.com Services, Inc",
|
|
343
320
|
item.seller.name)
|
|
344
321
|
self.assertIsNone(item.seller.link)
|
|
345
322
|
else:
|
|
346
323
|
found_aaa = True
|
|
347
|
-
# As of April 2024, this is no longer shown in Order History
|
|
348
|
-
# self.assertEqual("New", item.condition)
|
|
349
324
|
self.assertEqual(15.49, item.price)
|
|
350
325
|
self.assertEqual("Amazon.com Services, Inc",
|
|
351
326
|
item.seller.name)
|
|
@@ -353,6 +328,117 @@ class TestCase(unittest.TestCase):
|
|
|
353
328
|
self.assertTrue(found_aa)
|
|
354
329
|
self.assertTrue(found_aaa)
|
|
355
330
|
|
|
331
|
+
def assert_order_112_5939971_8962610_data_component(self, order, full_details=False):
|
|
332
|
+
self.assertEqual("112-5939971-8962610", order.order_number)
|
|
333
|
+
self.assertEqual(28.50, order.grand_total)
|
|
334
|
+
self.assertIsNotNone(order.order_details_link)
|
|
335
|
+
self.assertEqual(date(2024, 11, 1), order.order_placed_date)
|
|
336
|
+
self.assertEqual("Alex Laird", order.recipient.name)
|
|
337
|
+
self.assertIn("555 My Road", order.recipient.address)
|
|
338
|
+
self.assertEqual(1, len(order.shipments))
|
|
339
|
+
self.assertEqual(str(order.items),
|
|
340
|
+
str(order.shipments[0].items))
|
|
341
|
+
self.assertIsNotNone(order.shipments[0].tracking_link)
|
|
342
|
+
self.assertEqual("Delivered November 2", order.shipments[0].delivery_status)
|
|
343
|
+
self.assertEqual(1, len(order.items))
|
|
344
|
+
self.assertEqual("2 Set Replacement Parts Roller Brushes Compatible for iRobot Roomba E I and J Series, "
|
|
345
|
+
"Brush Replacement for iRobot Roomba i3 i3+ i6 i6+ i7 i7+ i8 i8+Plus E5 E6 E7 j7 j7+ evo "
|
|
346
|
+
"Vacuum Cleaner Accessories",
|
|
347
|
+
order.items[0].title)
|
|
348
|
+
self.assertEqual(2, order.items[0].quantity)
|
|
349
|
+
self.assertIsNotNone(order.items[0].link)
|
|
350
|
+
self.assertIsNotNone(order.items[0].image_link)
|
|
351
|
+
|
|
352
|
+
self.assertEqual(order.full_details, full_details)
|
|
353
|
+
|
|
354
|
+
if full_details:
|
|
355
|
+
self.assertEqual(date(2025, 1, 31), order.items[0].return_eligible_date)
|
|
356
|
+
self.assertEqual("American Express", order.payment_method)
|
|
357
|
+
self.assertEqual('1234', order.payment_method_last_4)
|
|
358
|
+
self.assertEqual(27.98, order.subtotal)
|
|
359
|
+
self.assertEqual(0.00, order.shipping_total)
|
|
360
|
+
self.assertEqual(26.58, order.total_before_tax)
|
|
361
|
+
self.assertEqual(1.92, order.estimated_tax)
|
|
362
|
+
self.assertEqual(13.99, order.items[0].price)
|
|
363
|
+
self.assertEqual("xianbaikeshang",
|
|
364
|
+
order.items[0].seller.name)
|
|
365
|
+
self.assertIsNotNone(order.items[0].seller.link)
|
|
366
|
+
|
|
367
|
+
def assert_order_112_4482432_2955442_gift_card(self, order, full_details=False):
|
|
368
|
+
self.assertEqual("112-4482432-2955442", order.order_number)
|
|
369
|
+
self.assertEqual(10.00, order.grand_total)
|
|
370
|
+
self.assertIsNotNone(order.order_details_link)
|
|
371
|
+
self.assertEqual(date(2024, 10, 30), order.order_placed_date)
|
|
372
|
+
self.assertIsNone(order.recipient)
|
|
373
|
+
self.assertEqual(0, len(order.shipments))
|
|
374
|
+
self.assertEqual(1, len(order.items))
|
|
375
|
+
self.assertEqual("Amazon eGift Card - Amazon Logo (Animated)",
|
|
376
|
+
order.items[0].title)
|
|
377
|
+
self.assertIsNotNone(order.items[0].link)
|
|
378
|
+
self.assertIsNotNone(order.items[0].image_link)
|
|
379
|
+
|
|
380
|
+
self.assertEqual(order.full_details, full_details)
|
|
381
|
+
|
|
382
|
+
if full_details:
|
|
383
|
+
self.assertEqual("American Express", order.payment_method)
|
|
384
|
+
self.assertEqual('1234', order.payment_method_last_4)
|
|
385
|
+
self.assertEqual(10.00, order.subtotal)
|
|
386
|
+
self.assertEqual(10.00, order.total_before_tax)
|
|
387
|
+
self.assertEqual(0.00, order.estimated_tax)
|
|
388
|
+
|
|
389
|
+
def assert_order_112_9087159_1657009_digital_order_legacy(self, order, full_details=False):
|
|
390
|
+
if full_details:
|
|
391
|
+
self.assertEqual(order.order_number, "D01-8711688-7680252")
|
|
392
|
+
else:
|
|
393
|
+
self.assertEqual(order.order_number, "112-9087159-1657009")
|
|
394
|
+
self.assertEqual(10.00, order.grand_total)
|
|
395
|
+
self.assertIsNotNone(order.order_details_link)
|
|
396
|
+
self.assertEqual(date(2024, 10, 30), order.order_placed_date)
|
|
397
|
+
self.assertEqual(0, len(order.shipments))
|
|
398
|
+
self.assertEqual(1, len(order.items))
|
|
399
|
+
self.assertEqual("$10 -PlayStation Store Gift Card [Digital Code]",
|
|
400
|
+
order.items[0].title)
|
|
401
|
+
self.assertIsNotNone(order.items[0].link)
|
|
402
|
+
self.assertIsNotNone(order.items[0].image_link)
|
|
403
|
+
|
|
404
|
+
self.assertEqual(order.full_details, full_details)
|
|
405
|
+
|
|
406
|
+
# We cannot parse full details for digital orders, so nothing to assert
|
|
407
|
+
|
|
408
|
+
def assert_order_114_8722141_6545058_data_component_subscription(self, order, full_details=False):
|
|
409
|
+
self.assertEqual("114-8722141-6545058", order.order_number)
|
|
410
|
+
self.assertEqual(44.46, order.grand_total)
|
|
411
|
+
self.assertIsNotNone(order.order_details_link)
|
|
412
|
+
self.assertEqual(date(2024, 10, 23), order.order_placed_date)
|
|
413
|
+
self.assertEqual("Alex Laird", order.recipient.name)
|
|
414
|
+
self.assertIn("555 My Road", order.recipient.address)
|
|
415
|
+
self.assertEqual(1, len(order.shipments))
|
|
416
|
+
self.assertEqual(str(order.items),
|
|
417
|
+
str(order.shipments[0].items))
|
|
418
|
+
self.assertIsNotNone(order.shipments[0].tracking_link)
|
|
419
|
+
self.assertEqual("Delivered October 26", order.shipments[0].delivery_status)
|
|
420
|
+
self.assertEqual(1, len(order.items))
|
|
421
|
+
self.assertEqual("Bounty Paper Towels Quick Size, White, 16 Family Rolls = 40 Regular Rolls",
|
|
422
|
+
order.items[0].title)
|
|
423
|
+
self.assertIsNotNone(order.items[0].link)
|
|
424
|
+
self.assertIsNotNone(order.items[0].image_link)
|
|
425
|
+
|
|
426
|
+
self.assertEqual(order.full_details, full_details)
|
|
427
|
+
|
|
428
|
+
if full_details:
|
|
429
|
+
self.assertEqual(date(2024, 11, 25), order.items[0].return_eligible_date)
|
|
430
|
+
self.assertEqual("American Express", order.payment_method)
|
|
431
|
+
self.assertEqual('1234', order.payment_method_last_4)
|
|
432
|
+
self.assertEqual(43.49, order.subtotal)
|
|
433
|
+
self.assertEqual(0.00, order.shipping_total)
|
|
434
|
+
self.assertEqual(-2.17, order.subscription_discount)
|
|
435
|
+
self.assertEqual(41.32, order.total_before_tax)
|
|
436
|
+
self.assertEqual(3.14, order.estimated_tax)
|
|
437
|
+
self.assertEqual(43.49, order.items[0].price)
|
|
438
|
+
self.assertEqual("Amazon.com",
|
|
439
|
+
order.items[0].seller.name)
|
|
440
|
+
self.assertIsNone(order.items[0].seller.link)
|
|
441
|
+
|
|
356
442
|
def assert_populated_generic(self, order, full_details):
|
|
357
443
|
self.assertIsNotNone(order.order_number)
|
|
358
444
|
self.assertIsNotNone(order.grand_total)
|
|
@@ -377,8 +463,6 @@ class TestCase(unittest.TestCase):
|
|
|
377
463
|
self.assertIsNotNone(order.shipping_total)
|
|
378
464
|
self.assertIsNotNone(order.total_before_tax)
|
|
379
465
|
self.assertIsNotNone(order.estimated_tax)
|
|
380
|
-
# As of April 2024, this is no longer shown in Order History
|
|
381
|
-
# self.assertIsNotNone(order.items[0].condition)
|
|
382
466
|
if order.recipient:
|
|
383
467
|
self.assertIsNotNone(order.items[0].price)
|
|
384
468
|
self.assertIsNotNone(order.items[0].seller.name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|