amazon-orders 3.2.0__tar.gz → 3.2.2__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.
Files changed (40) hide show
  1. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/CHANGELOG.md +18 -1
  2. {amazon_orders-3.2.0/amazon_orders.egg-info → amazon_orders-3.2.2}/PKG-INFO +2 -2
  3. {amazon_orders-3.2.0 → amazon_orders-3.2.2/amazon_orders.egg-info}/PKG-INFO +2 -2
  4. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/__init__.py +1 -1
  5. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/orders.py +6 -0
  6. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/selectors.py +40 -30
  7. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/transactions.py +20 -40
  8. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/tests/test_cli.py +2 -2
  9. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/tests/test_orders.py +45 -0
  10. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/tests/test_transactions.py +5 -5
  11. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/tests/testcase.py +59 -7
  12. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/LICENSE +0 -0
  13. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/MANIFEST.in +0 -0
  14. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/README.md +0 -0
  15. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazon_orders.egg-info/SOURCES.txt +0 -0
  16. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazon_orders.egg-info/dependency_links.txt +0 -0
  17. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazon_orders.egg-info/entry_points.txt +0 -0
  18. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazon_orders.egg-info/requires.txt +0 -0
  19. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazon_orders.egg-info/top_level.txt +0 -0
  20. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/banner.txt +0 -0
  21. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/cli.py +0 -0
  22. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/conf.py +0 -0
  23. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/constants.py +0 -0
  24. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/__init__.py +0 -0
  25. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/item.py +0 -0
  26. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/order.py +0 -0
  27. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/parsable.py +0 -0
  28. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/recipient.py +0 -0
  29. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/seller.py +0 -0
  30. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/shipment.py +0 -0
  31. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/entity/transaction.py +0 -0
  32. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/exception.py +0 -0
  33. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/forms.py +0 -0
  34. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/session.py +0 -0
  35. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/amazonorders/util.py +0 -0
  36. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/pyproject.toml +0 -0
  37. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/setup.cfg +0 -0
  38. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/tests/test_conf.py +0 -0
  39. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/tests/test_session.py +0 -0
  40. {amazon_orders-3.2.0 → amazon_orders-3.2.2}/tests/test_util.py +0 -0
@@ -4,7 +4,24 @@ 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/3.2.0...HEAD)
7
+ ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/3.2.2...HEAD)
8
+
9
+ ## [3.2.2](https://github.com/alexdlaird/amazon-orders/compare/3.2.1...3.2.2) - 2025-01-28
10
+
11
+ ### Added
12
+
13
+ - Stability improvements.
14
+
15
+ ### Fixed
16
+
17
+ - Broken parsing of Amazon Fresh orders, these are now skipped.
18
+
19
+ ## [3.2.1](https://github.com/alexdlaird/amazon-orders/compare/3.2.0...3.2.1) - 2024-11-08
20
+
21
+ ### Fixed
22
+
23
+ - Issues with parsing Items and Shipments using the new `data-component`, selectors made more precise.
24
+ - Transactions use `util` selector methods, so consistent use of trying a list of selectors is maintained.
8
25
 
9
26
  ## [3.2.0](https://github.com/alexdlaird/amazon-orders/compare/3.1.0...3.2.0) - 2024-11-07
10
27
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: A CLI and library for interacting with Amazon order history.
5
5
  Maintainer-email: Alex Laird <contact@alexlaird.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: A CLI and library for interacting with Amazon order history.
5
5
  Maintainer-email: Alex Laird <contact@alexlaird.com>
6
6
  License: MIT License
@@ -1,3 +1,3 @@
1
1
  __copyright__ = "Copyright (c) 2024 Alex Laird"
2
2
  __license__ = "MIT"
3
- __version__ = "3.2.0"
3
+ __version__ = "3.2.2"
@@ -73,6 +73,12 @@ class AmazonOrders:
73
73
  response_parsed = self.amazon_session.last_response_parsed
74
74
 
75
75
  for order_tag in util.select(response_parsed, self.config.selectors.ORDER_HISTORY_ENTITY_SELECTOR):
76
+ # If we find a brand logo (for instance, Amazon Fresh), we don't know how to parse this. If we know how
77
+ # to do this in the future, we can implement it, but right now we have no reliable way, so skipping
78
+ # these orders.
79
+ if util.select(order_tag, self.config.selectors.ORDER_HISTORY_BRAND_SELECTOR):
80
+ continue
81
+
76
82
  order: Order = self.config.order_cls(order_tag, self.config)
77
83
 
78
84
  if full_details:
@@ -45,20 +45,31 @@ class Selectors:
45
45
  # is passed.
46
46
  ##########################################################################
47
47
 
48
- ORDER_HISTORY_ENTITY_SELECTOR = ["div.order-card", "div.order"]
49
- ORDER_DETAILS_ENTITY_SELECTOR = ["div#orderDetails", "div#ordersContainer"]
50
- ITEM_ENTITY_SELECTOR = ["div:has(> div.yohtmlc-item)", ".item-box", "[data-component='purchasedItems']"]
51
- SHIPMENT_ENTITY_SELECTOR = ["div.shipment", "div.delivery-box",
52
- "[data-component='orderCard'] [data-component='shipments']"]
48
+ ORDER_HISTORY_ENTITY_SELECTOR = ["div.order-card",
49
+ "div.order"]
50
+ ORDER_DETAILS_ENTITY_SELECTOR = ["div#orderDetails",
51
+ "div#ordersContainer"]
52
+ ITEM_ENTITY_SELECTOR = ["[data-component='purchasedItems'] .a-fixed-left-grid",
53
+ "div:has(> div.yohtmlc-item)",
54
+ ".item-box"]
55
+ SHIPMENT_ENTITY_SELECTOR = ["[data-component='orderCard'] [data-component='shipments'] .a-box",
56
+ "div.shipment",
57
+ "div.delivery-box"]
58
+ ORDER_HISTORY_BRAND_SELECTOR = ".brand-info-box .brand-logo img"
53
59
 
54
60
  #####################################
55
61
  # CSS selectors for Item fields
56
62
  #####################################
57
63
 
58
64
  FIELD_ITEM_IMG_LINK_SELECTOR = "a img"
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)"]
65
+ FIELD_ITEM_QUANTITY_SELECTOR = [".od-item-view-qty",
66
+ "span.item-view-qty",
67
+ "span.product-image__qty"]
68
+ FIELD_ITEM_TITLE_SELECTOR = ["[data-component='itemTitle']",
69
+ ".yohtmlc-item a", ".yohtmlc-product-title"]
70
+ FIELD_ITEM_LINK_SELECTOR = ["[data-component='itemTitle'] a",
71
+ ".yohtmlc-item a",
72
+ "a:has(> .yohtmlc-product-title)"]
62
73
  FIELD_ITEM_TAG_ITERATOR_SELECTOR = [".yohtmlc-item div"]
63
74
  FIELD_ITEM_PRICE_SELECTOR = ["[data-component='unitPrice'] .a-text-price :not(.a-offscreen)",
64
75
  ".yohtmlc-item .a-color-price"]
@@ -70,9 +81,13 @@ class Selectors:
70
81
  #####################################
71
82
 
72
83
  FIELD_ORDER_DETAILS_LINK_SELECTOR = "a.yohtmlc-order-details-link"
73
- FIELD_ORDER_NUMBER_SELECTOR = [".order-date-invoice-item bdi[dir='ltr']", "bdi[dir='ltr']", "span[dir='ltr']"]
74
- FIELD_ORDER_GRAND_TOTAL_SELECTOR = ["div.yohtmlc-order-total span.value", "div.order-header div.a-column.a-span2"]
75
- FIELD_ORDER_PLACED_DATE_SELECTOR = ["span.order-date-invoice-item", "div.a-span3"]
84
+ FIELD_ORDER_NUMBER_SELECTOR = [".order-date-invoice-item bdi[dir='ltr']",
85
+ "bdi[dir='ltr']",
86
+ "span[dir='ltr']"]
87
+ FIELD_ORDER_GRAND_TOTAL_SELECTOR = ["div.yohtmlc-order-total span.value",
88
+ "div.order-header div.a-column.a-span2"]
89
+ FIELD_ORDER_PLACED_DATE_SELECTOR = ["span.order-date-invoice-item",
90
+ "div.a-span3"]
76
91
  FIELD_ORDER_PAYMENT_METHOD_SELECTOR = "img.pmts-payment-credit-card-instrument-logo"
77
92
  FIELD_ORDER_PAYMENT_METHOD_LAST_4_SELECTOR = "span:has(img.pmts-payment-credit-card-instrument-logo):last-child"
78
93
  FIELD_ORDER_SUBTOTALS_TAG_ITERATOR_SELECTOR = ["[data-component='orderSubtotals'] div.a-row",
@@ -88,7 +103,8 @@ class Selectors:
88
103
  # CSS selectors for Shipment fields
89
104
  #####################################
90
105
 
91
- FIELD_SHIPMENT_TRACKING_LINK_SELECTOR = ["span.track-package-button a", "a[href*='ship-track?itemId=']"]
106
+ FIELD_SHIPMENT_TRACKING_LINK_SELECTOR = ["span.track-package-button a",
107
+ "a[href*='ship-track?itemId=']"]
92
108
  FIELD_SHIPMENT_DELIVERY_STATUS_SELECTOR = ["div.js-shipment-info-container div.a-row",
93
109
  "span.delivery-box__primary-text",
94
110
  ".yohtmlc-shipment-status-primaryText",
@@ -122,25 +138,19 @@ class Selectors:
122
138
  TRANSACTIONS_CONTAINER_SELECTOR = "div"
123
139
  TRANSACTIONS_SELECTOR = "div.apx-transactions-line-item-component-container"
124
140
 
125
- TRANSACTIONS_NEXT_PAGE_INPUT_SELECTOR = (
126
- "input[type='submit'][name^='ppw-widgetEvent:DefaultNextPageNavigationEvent']"
127
- )
141
+ TRANSACTIONS_NEXT_PAGE_INPUT_SELECTOR = [
142
+ "input[type='submit'][name^='ppw-widgetEvent:DefaultNextPageNavigationEvent']"]
128
143
  TRANSACTIONS_NEXT_PAGE_INPUT_STATE_SELECTOR = "input[name='ppw-widgetState']"
129
144
  TRANSACTIONS_NEXT_PAGE_INPUT_IE_SELECTOR = "input[name='ie']"
130
145
 
131
146
  FIELD_TRANSACTION_COMPLETED_DATE_SELECTOR = "span"
132
- FIELD_TRANSACTION_PAYMENT_METHOD_SELECTOR = (
133
- "div.apx-transactions-line-item-component-container > div:nth-child(1) span.a-size-base"
134
- )
135
- FIELD_TRANSACTION_GRAND_TOTAL_SELECTOR = (
136
- "div.apx-transactions-line-item-component-container > div:nth-child(1) span.a-size-base-plus"
137
- )
138
- FIELD_TRANSACTION_ORDER_NUMBER_SELECTOR = (
139
- "div.apx-transactions-line-item-component-container > div:nth-child(2) a.a-link-normal"
140
- )
141
- FIELD_TRANSACTION_ORDER_LINK_SELECTOR = (
142
- "div.apx-transactions-line-item-component-container > div:nth-child(2) a.a-link-normal"
143
- )
144
- FIELD_TRANSACTION_SELLER_NAME_SELECTOR = (
145
- "div.apx-transactions-line-item-component-container > div:nth-child(3) span.a-size-base"
146
- )
147
+ FIELD_TRANSACTION_PAYMENT_METHOD_SELECTOR = [
148
+ "div.apx-transactions-line-item-component-container > div:nth-child(1) span.a-size-base"]
149
+ FIELD_TRANSACTION_GRAND_TOTAL_SELECTOR = [
150
+ "div.apx-transactions-line-item-component-container > div:nth-child(1) span.a-size-base-plus"]
151
+ FIELD_TRANSACTION_ORDER_NUMBER_SELECTOR = [
152
+ "div.apx-transactions-line-item-component-container > div:nth-child(2) a.a-link-normal"]
153
+ FIELD_TRANSACTION_ORDER_LINK_SELECTOR = [
154
+ "div.apx-transactions-line-item-component-container > div:nth-child(2) a.a-link-normal"]
155
+ FIELD_TRANSACTION_SELLER_NAME_SELECTOR = [
156
+ "div.apx-transactions-line-item-component-container > div:nth-child(3) span.a-size-base"]
@@ -8,6 +8,7 @@ from typing import Dict, List, Optional, Tuple
8
8
  from bs4 import Tag
9
9
  from dateutil import parser
10
10
 
11
+ from amazonorders import util
11
12
  from amazonorders.conf import AmazonOrdersConfig
12
13
  from amazonorders.entity.transaction import Transaction
13
14
  from amazonorders.exception import AmazonOrdersError
@@ -16,55 +17,36 @@ from amazonorders.session import AmazonSession
16
17
  logger = logging.getLogger(__name__)
17
18
 
18
19
 
19
- def _get_today() -> datetime.date:
20
- return datetime.date.today()
21
-
22
-
23
- def _parse_transaction_form_tag(
24
- form_tag: Tag, config: AmazonOrdersConfig
25
- ) -> Tuple[List[Transaction], Optional[str], Optional[Dict[str, str]]]:
20
+ def _parse_transaction_form_tag(form_tag: Tag,
21
+ config: AmazonOrdersConfig) \
22
+ -> Tuple[List[Transaction], Optional[str], Optional[Dict[str, str]]]:
26
23
  transactions = []
27
- date_container_tags = form_tag.select(
28
- config.selectors.TRANSACTION_DATE_CONTAINERS_SELECTOR
29
- )
24
+ date_container_tags = util.select(form_tag, config.selectors.TRANSACTION_DATE_CONTAINERS_SELECTOR)
30
25
  for date_container_tag in date_container_tags:
31
- date_tag = date_container_tag.select_one(
32
- config.selectors.FIELD_TRANSACTION_COMPLETED_DATE_SELECTOR
33
- )
26
+ date_tag = util.select_one(date_container_tag, config.selectors.FIELD_TRANSACTION_COMPLETED_DATE_SELECTOR)
34
27
  if not date_tag:
35
- logger.warning("Could not find date tag in transaction form.")
28
+ logger.warning("Could not find date tag in Transaction form.")
36
29
  continue
37
30
 
38
31
  date_str = date_tag.text
39
32
  date = parser.parse(date_str).date()
40
33
 
41
34
  transactions_container_tag = date_container_tag.find_next_sibling(
42
- config.selectors.TRANSACTIONS_CONTAINER_SELECTOR
43
- )
35
+ config.selectors.TRANSACTIONS_CONTAINER_SELECTOR)
44
36
  if not isinstance(transactions_container_tag, Tag):
45
- logger.warning(
46
- "Could not find transactions container tag in transaction form."
47
- )
37
+ logger.warning("Could not find transactions container tag in Transaction form.")
48
38
  continue
49
39
 
50
- transaction_tags = transactions_container_tag.select(
51
- config.selectors.TRANSACTIONS_SELECTOR
52
- )
40
+ transaction_tags = util.select(transactions_container_tag, config.selectors.TRANSACTIONS_SELECTOR)
53
41
  for transaction_tag in transaction_tags:
54
42
  transaction = Transaction(transaction_tag, config, date)
55
43
  transactions.append(transaction)
56
44
 
57
- form_state_input = form_tag.select_one(
58
- config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_STATE_SELECTOR
59
- )
60
- form_ie_input = form_tag.select_one(
61
- config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_IE_SELECTOR
62
- )
63
- next_page_input = form_tag.select_one(
64
- config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_SELECTOR
65
- )
45
+ form_state_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_STATE_SELECTOR)
46
+ form_ie_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_IE_SELECTOR)
47
+ next_page_input = util.select_one(form_tag, config.selectors.TRANSACTIONS_NEXT_PAGE_INPUT_SELECTOR)
66
48
  if not next_page_input or not form_state_input or not form_ie_input:
67
- return (transactions, None, None)
49
+ return transactions, None, None
68
50
 
69
51
  next_page_post_url = str(form_tag["action"])
70
52
  next_page_post_data = {
@@ -73,7 +55,7 @@ def _parse_transaction_form_tag(
73
55
  str(next_page_input["name"]): "",
74
56
  }
75
57
 
76
- return (transactions, next_page_post_url, next_page_post_data)
58
+ return transactions, next_page_post_url, next_page_post_data
77
59
 
78
60
 
79
61
  class AmazonTransactions:
@@ -112,15 +94,14 @@ class AmazonTransactions:
112
94
  if not self.amazon_session.is_authenticated:
113
95
  raise AmazonOrdersError("Call AmazonSession.login() to authenticate first.")
114
96
 
115
- min_date = _get_today() - datetime.timedelta(days=days)
97
+ min_date = datetime.date.today() - datetime.timedelta(days=days)
116
98
 
117
99
  self.amazon_session.get(self.config.constants.TRANSACTION_HISTORY_LANDING_URL)
118
100
  if not self.amazon_session.last_response_parsed:
119
101
  raise AmazonOrdersError("Could not get transaction history landing page.")
120
102
 
121
- form_tag = self.amazon_session.last_response_parsed.select_one(
122
- self.config.selectors.TRANSACTION_HISTORY_FORM_SELECTOR
123
- )
103
+ form_tag = util.select_one(self.amazon_session.last_response_parsed,
104
+ self.config.selectors.TRANSACTION_HISTORY_FORM_SELECTOR)
124
105
 
125
106
  transactions: List[Transaction] = []
126
107
  while form_tag:
@@ -140,8 +121,7 @@ class AmazonTransactions:
140
121
  if not self.amazon_session.last_response_parsed:
141
122
  raise AmazonOrdersError("Could not get next transaction history page.")
142
123
 
143
- form_tag = self.amazon_session.last_response_parsed.select_one(
144
- self.config.selectors.TRANSACTION_HISTORY_FORM_SELECTOR
145
- )
124
+ form_tag = util.select_one(self.amazon_session.last_response_parsed,
125
+ self.config.selectors.TRANSACTION_HISTORY_FORM_SELECTOR)
146
126
 
147
127
  return transactions
@@ -86,10 +86,10 @@ class TestCli(UnitTestCase):
86
86
  self.assertIn("Order #112-2961628-4757846", response.output)
87
87
 
88
88
  @responses.activate
89
- @patch("amazonorders.transactions._get_today")
89
+ @patch("amazonorders.transactions.datetime", wraps=datetime)
90
90
  def test_transactions_command(self, mock_get_today: Mock):
91
91
  # GIVEN
92
- mock_get_today.return_value = datetime.date(2024, 10, 11)
92
+ mock_get_today.date.today.return_value = datetime.date(2024, 10, 11)
93
93
  days = 1
94
94
  self.given_login_responses_success()
95
95
  with open(os.path.join(self.RESOURCES_DIR, "get-transactions.html"), "r", encoding="utf-8") as f:
@@ -103,6 +103,30 @@ class TestOrders(UnitTestCase):
103
103
  self.assertEqual(1, resp2.call_count)
104
104
  self.assertEqual(1, resp3.call_count)
105
105
 
106
+ @responses.activate
107
+ def test_get_order_history_skip_fresh(self):
108
+ # GIVEN
109
+ self.amazon_session.is_authenticated = True
110
+ year = 2024
111
+ start_index = 0
112
+ resp1 = self.given_order_history_landing_exists()
113
+ with open(os.path.join(self.RESOURCES_DIR, "order-history-fresh.html"), "r",
114
+ encoding="utf-8") as f:
115
+ resp2 = responses.add(
116
+ responses.GET,
117
+ self.test_config.constants.ORDER_HISTORY_URL,
118
+ body=f.read(),
119
+ status=200,
120
+ )
121
+
122
+ # WHEN
123
+ orders = self.amazon_orders.get_order_history(year=year, start_index=start_index)
124
+
125
+ # THEN
126
+ self.assertEqual(9, len(orders))
127
+ self.assertEqual(1, resp1.call_count)
128
+ self.assertEqual(1, resp2.call_count)
129
+
106
130
  @responses.activate
107
131
  def test_get_order_history_full_details(self):
108
132
  # GIVEN
@@ -308,6 +332,27 @@ class TestOrders(UnitTestCase):
308
332
  self.assert_order_114_8722141_6545058_data_component_subscription(order, True)
309
333
  self.assertEqual(1, resp1.call_count)
310
334
 
335
+ @responses.activate
336
+ def test_get_order_2024_data_component_multiple_shipments(self):
337
+ # GIVEN
338
+ self.amazon_session.is_authenticated = True
339
+ order_id = "111-6778632-7354601"
340
+ with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
341
+ encoding="utf-8") as f:
342
+ resp1 = responses.add(
343
+ responses.GET,
344
+ f"{self.test_config.constants.ORDER_DETAILS_URL}?orderID={order_id}",
345
+ body=f.read(),
346
+ status=200,
347
+ )
348
+
349
+ # WHEN
350
+ order = self.amazon_orders.get_order(order_id)
351
+
352
+ # THEN
353
+ self.assert_order_111_6778632_7354601_data_component_subscription(order, True)
354
+ self.assertEqual(1, resp1.call_count)
355
+
311
356
  @unittest.skipIf(not os.path.exists(temp_order_history_file_path),
312
357
  reason="Skipped, to debug an order history page, "
313
358
  "place it at tests/output/temp-order-history.html")
@@ -31,16 +31,16 @@ class TestOrders(UnitTestCase):
31
31
  self.amazon_transactions = AmazonTransactions(self.amazon_session)
32
32
 
33
33
  @responses.activate
34
- @patch("amazonorders.transactions._get_today")
34
+ @patch("amazonorders.transactions.datetime", wraps=datetime)
35
35
  def test_transactions_command(self, mock_get_today: Mock):
36
36
  # GIVEN
37
- mock_get_today.return_value = datetime.date(2024, 10, 11)
37
+ mock_get_today.date.today.return_value = datetime.date(2024, 10, 11)
38
38
  days = 1
39
39
  self.amazon_session.is_authenticated = True
40
40
  with open(
41
- os.path.join(self.RESOURCES_DIR, "get-transactions.html"),
42
- "r",
43
- encoding="utf-8",
41
+ os.path.join(self.RESOURCES_DIR, "get-transactions.html"),
42
+ "r",
43
+ encoding="utf-8",
44
44
  ) as f:
45
45
  responses.add(
46
46
  responses.GET,
@@ -217,8 +217,8 @@ class TestCase(unittest.TestCase):
217
217
  self.assertIn("Delivered May 13", shipment.delivery_status)
218
218
  self.assertTrue(found_kimoe)
219
219
  self.assertTrue(found_amazon)
220
- self.assertEqual(str(order.items),
221
- str(order.shipments[0].items + order.shipments[1].items))
220
+ self.assertEqual(str(order.items.sort()),
221
+ str((order.shipments[0].items + order.shipments[1].items).sort()))
222
222
  self.assertEqual(2, len(order.items))
223
223
  found_kimoe = False
224
224
  found_amazon = False
@@ -439,6 +439,51 @@ class TestCase(unittest.TestCase):
439
439
  order.items[0].seller.name)
440
440
  self.assertIsNone(order.items[0].seller.link)
441
441
 
442
+ def assert_order_111_6778632_7354601_data_component_subscription(self, order, full_details=False):
443
+ self.assertEqual("111-6778632-7354601", order.order_number)
444
+ self.assertEqual(60.88, order.grand_total)
445
+ self.assertIsNotNone(order.order_details_link)
446
+ self.assertEqual(date(2024, 9, 8), order.order_placed_date)
447
+ self.assertEqual("Name1 & Name2", order.recipient.name)
448
+ self.assertIn("Address2", order.recipient.address)
449
+ self.assertEqual(2, len(order.shipments))
450
+ self.assertEqual(str(order.items.sort()),
451
+ str((order.shipments[0].items + order.shipments[1].items).sort()))
452
+ self.assertEqual(3, len(order.shipments[0].items))
453
+ self.assertEqual(1, len(order.shipments[1].items))
454
+ self.assertEqual("Delivered September 9", order.shipments[0].delivery_status)
455
+ self.assertEqual("Delivered September 9", order.shipments[1].delivery_status)
456
+ self.assertEqual(4, len(order.items))
457
+ self.assertEqual(
458
+ "Dxhycc Satin Pirate Sash Pirate Medieval Renaissance Large Sash Halloween Costume Waist "
459
+ "Sash Belt, Red",
460
+ order.items[0].title)
461
+ self.assertEqual("Ziploc Paper Sandwich and Snack Bags, Recyclable & Sealable with Fun Designs, "
462
+ "150 Total Bags",
463
+ order.items[3].title)
464
+ self.assertIsNotNone(order.items[0].link)
465
+ self.assertIsNotNone(order.items[0].image_link)
466
+ self.assertIsNotNone(order.items[3].link)
467
+ self.assertIsNotNone(order.items[3].image_link)
468
+
469
+ self.assertEqual(order.full_details, full_details)
470
+
471
+ if full_details:
472
+ self.assertEqual("Prime Visa", order.payment_method)
473
+ self.assertEqual(1111, order.payment_method_last_4)
474
+ self.assertEqual(57.69, order.subtotal)
475
+ self.assertEqual(2.99, order.shipping_total)
476
+ self.assertEqual(57.69, order.total_before_tax)
477
+ self.assertEqual(3.19, order.estimated_tax)
478
+ self.assertEqual(date(2024, 10, 9), order.items[0].return_eligible_date)
479
+ self.assertEqual(date(2024, 10, 9), order.items[1].return_eligible_date)
480
+ self.assertEqual(date(2024, 10, 9), order.items[2].return_eligible_date)
481
+ self.assertEqual(date(2024, 10, 9), order.items[3].return_eligible_date)
482
+ self.assertEqual(7.49, order.items[0].price)
483
+ self.assertEqual(18.95, order.items[1].price)
484
+ self.assertEqual(9.98, order.items[2].price)
485
+ self.assertEqual(21.27, order.items[3].price)
486
+
442
487
  def assert_populated_generic(self, order, full_details):
443
488
  self.assertIsNotNone(order.order_number)
444
489
  self.assertIsNotNone(order.grand_total)
@@ -448,10 +493,16 @@ class TestCase(unittest.TestCase):
448
493
  self.assertIsNotNone(order.recipient.name)
449
494
  self.assertIsNotNone(order.recipient.address)
450
495
  self.assertGreaterEqual(len(order.shipments), 1)
451
- self.assertEqual(str(order.items), str(order.shipments[0].items))
496
+ shipment_items = []
497
+ for shipment in order.shipments:
498
+ shipment_items += shipment.items
499
+ self.assertGreaterEqual(len(shipment.items), 1)
500
+ self.assertIsNotNone(shipment.delivery_status)
501
+ self.assertEqual(str(order.items.sort()), str(shipment_items.sort()))
452
502
  self.assertGreaterEqual(len(order.items), 1)
453
- self.assertIsNotNone(order.items[0].title)
454
- self.assertIsNotNone(order.items[0].link)
503
+ for item in order.items:
504
+ self.assertIsNotNone(item.title)
505
+ self.assertIsNotNone(item.link)
455
506
 
456
507
  self.assertEqual(order.full_details, full_details)
457
508
 
@@ -464,5 +515,6 @@ class TestCase(unittest.TestCase):
464
515
  self.assertIsNotNone(order.total_before_tax)
465
516
  self.assertIsNotNone(order.estimated_tax)
466
517
  if order.recipient:
467
- self.assertIsNotNone(order.items[0].price)
468
- self.assertIsNotNone(order.items[0].seller.name)
518
+ for item in order.items:
519
+ self.assertIsNotNone(item.price)
520
+ self.assertIsNotNone(item.seller.name)
File without changes
File without changes
File without changes
File without changes