amazon-orders 3.2.4__tar.gz → 3.2.6__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 (41) hide show
  1. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/CHANGELOG.md +18 -1
  2. {amazon_orders-3.2.4/amazon_orders.egg-info → amazon_orders-3.2.6}/PKG-INFO +1 -1
  3. {amazon_orders-3.2.4 → amazon_orders-3.2.6/amazon_orders.egg-info}/PKG-INFO +1 -1
  4. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/__init__.py +1 -1
  5. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/transaction.py +1 -1
  6. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/selectors.py +5 -3
  7. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/transactions.py +1 -1
  8. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/tests/test_cli.py +3 -2
  9. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/tests/test_orders.py +44 -9
  10. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/tests/test_session.py +30 -26
  11. amazon_orders-3.2.6/tests/test_transactions.py +137 -0
  12. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/tests/test_util.py +1 -1
  13. amazon_orders-3.2.4/tests/test_transactions.py +0 -166
  14. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/LICENSE +0 -0
  15. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/MANIFEST.in +0 -0
  16. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/README.md +0 -0
  17. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazon_orders.egg-info/SOURCES.txt +0 -0
  18. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazon_orders.egg-info/dependency_links.txt +0 -0
  19. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazon_orders.egg-info/entry_points.txt +0 -0
  20. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazon_orders.egg-info/requires.txt +0 -0
  21. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazon_orders.egg-info/top_level.txt +0 -0
  22. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/banner.txt +0 -0
  23. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/cli.py +0 -0
  24. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/conf.py +0 -0
  25. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/constants.py +0 -0
  26. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/__init__.py +0 -0
  27. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/item.py +0 -0
  28. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/order.py +0 -0
  29. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/parsable.py +0 -0
  30. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/recipient.py +0 -0
  31. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/seller.py +0 -0
  32. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/entity/shipment.py +0 -0
  33. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/exception.py +0 -0
  34. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/forms.py +0 -0
  35. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/orders.py +0 -0
  36. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/session.py +0 -0
  37. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/amazonorders/util.py +0 -0
  38. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/pyproject.toml +0 -0
  39. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/setup.cfg +0 -0
  40. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/tests/test_conf.py +0 -0
  41. {amazon_orders-3.2.4 → amazon_orders-3.2.6}/tests/testcase.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.4...HEAD)
7
+ ## [Unreleased](https://github.com/alexdlaird/amazon-orders/compare/3.2.6...HEAD)
8
+
9
+ ## [3.2.6](https://github.com/alexdlaird/amazon-orders/compare/3.2.5...3.2.6) - 2025-02-17
10
+
11
+ ### Added
12
+
13
+ - Add generic integration tests for Transactions, now in weekly run.
14
+ - Other test improvements.
15
+
16
+ ### Fixed
17
+
18
+ - Broken parsing when Transaction is pending.
19
+
20
+ ## [3.2.5](https://github.com/alexdlaird/amazon-orders/compare/3.2.4...3.2.5) - 2025-02-12
21
+
22
+ ### Fixed
23
+
24
+ - Parsing errors on gift cards totals and broken item links due to changes in Amazon.com DOM.
8
25
 
9
26
  ## [3.2.4](https://github.com/alexdlaird/amazon-orders/compare/3.2.3...3.2.4) - 2025-02-11
10
27
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.4
3
+ Version: 3.2.6
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
1
  Metadata-Version: 2.2
2
2
  Name: amazon-orders
3
- Version: 3.2.4
3
+ Version: 3.2.6
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.4"
3
+ __version__ = "3.2.6"
@@ -1,4 +1,4 @@
1
- __copyright__ = "Copyright (c) 2024 Jeff Sawatzky"
1
+ __copyright__ = "Copyright (c) 2024 Alex Laird"
2
2
  __license__ = "MIT"
3
3
 
4
4
  import logging
@@ -76,7 +76,8 @@ class Selectors:
76
76
  ".yohtmlc-item a", ".yohtmlc-product-title"]
77
77
  FIELD_ITEM_LINK_SELECTOR = ["[data-component='itemTitle'] a",
78
78
  ".yohtmlc-item a",
79
- "a:has(> .yohtmlc-product-title)"]
79
+ "a:has(> .yohtmlc-product-title)",
80
+ ".yohtmlc-product-title a"]
80
81
  FIELD_ITEM_TAG_ITERATOR_SELECTOR = [".yohtmlc-item div"]
81
82
  FIELD_ITEM_PRICE_SELECTOR = ["[data-component='unitPrice'] .a-text-price :not(.a-offscreen)",
82
83
  ".yohtmlc-item .a-color-price"]
@@ -93,7 +94,8 @@ class Selectors:
93
94
  "bdi[dir='ltr']",
94
95
  "span[dir='ltr']"]
95
96
  FIELD_ORDER_GRAND_TOTAL_SELECTOR = ["div.yohtmlc-order-total span.value",
96
- "div.order-header div.a-column.a-span2"]
97
+ "div.order-header div.a-column.a-span2",
98
+ "div.order-header div.a-col-left .a-span9"]
97
99
  FIELD_ORDER_PLACED_DATE_SELECTOR = ["[data-component='briefOrderInfo'] div.a-column",
98
100
  "span.order-date-invoice-item",
99
101
  "div.a-span3"]
@@ -158,7 +160,7 @@ class Selectors:
158
160
  FIELD_TRANSACTION_GRAND_TOTAL_SELECTOR = [
159
161
  "div.apx-transactions-line-item-component-container > div:nth-child(1) span.a-size-base-plus"]
160
162
  FIELD_TRANSACTION_ORDER_NUMBER_SELECTOR = [
161
- "div.apx-transactions-line-item-component-container > div:nth-child(2) a.a-link-normal"]
163
+ "div.apx-transactions-line-item-component-container div .a-span12 a"]
162
164
  FIELD_TRANSACTION_ORDER_LINK_SELECTOR = [
163
165
  "div.apx-transactions-line-item-component-container > div:nth-child(2) a.a-link-normal"]
164
166
  FIELD_TRANSACTION_SELLER_NAME_SELECTOR = [
@@ -1,4 +1,4 @@
1
- __copyright__ = "Copyright (c) 2024 Jeff Sawatzky"
1
+ __copyright__ = "Copyright (c) 2024 Alex Laird"
2
2
  __license__ = "MIT"
3
3
 
4
4
  import datetime
@@ -65,7 +65,7 @@ class TestCli(UnitTestCase):
65
65
  # GIVEN
66
66
  order_id = "112-2961628-4757846"
67
67
  self.given_login_responses_success()
68
- with open(os.path.join(self.RESOURCES_DIR, "order-details-112-2961628-4757846.html"), "r",
68
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-details-112-2961628-4757846.html"), "r",
69
69
  encoding="utf-8") as f:
70
70
  resp1 = responses.add(
71
71
  responses.GET,
@@ -92,7 +92,8 @@ class TestCli(UnitTestCase):
92
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
- with open(os.path.join(self.RESOURCES_DIR, "get-transactions.html"), "r", encoding="utf-8") as f:
95
+ with open(os.path.join(self.RESOURCES_DIR, "transactions", "get-transactions.html"),
96
+ "r", encoding="utf-8") as f:
96
97
  resp = responses.add(
97
98
  responses.GET,
98
99
  f"{self.test_config.constants.TRANSACTION_HISTORY_LANDING_URL}",
@@ -3,6 +3,7 @@ __license__ = "MIT"
3
3
 
4
4
  import os
5
5
  import unittest
6
+ from datetime import date
6
7
 
7
8
  import responses
8
9
 
@@ -77,6 +78,40 @@ class TestOrders(UnitTestCase):
77
78
  self.assertEqual(1, resp1.call_count)
78
79
  self.assertEqual(1, resp2.call_count)
79
80
 
81
+ @responses.activate
82
+ def test_get_order_history_2025_gift_card(self):
83
+ # GIVEN
84
+ self.amazon_session.is_authenticated = True
85
+ year = 2024
86
+ start_index = 0
87
+ resp1 = self.given_order_history_landing_exists()
88
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-egift.html"), "r",
89
+ encoding="utf-8") as f:
90
+ resp2 = responses.add(
91
+ responses.GET,
92
+ self.test_config.constants.ORDER_HISTORY_URL,
93
+ body=f.read(),
94
+ status=200,
95
+ )
96
+
97
+ # WHEN
98
+ orders = self.amazon_orders.get_order_history(year=year, start_index=start_index)
99
+
100
+ # THEN
101
+ self.assertEqual(9, len(orders))
102
+ self.assertEqual(1, resp1.call_count)
103
+ self.assertEqual(1, resp2.call_count)
104
+ order = orders[5]
105
+ self.assertEqual("112-8022032-9113020", order.order_number)
106
+ self.assertEqual(150.00, order.grand_total)
107
+ self.assertIsNotNone(order.order_details_link)
108
+ self.assertEqual(date(2024, 10, 28), order.order_placed_date)
109
+ self.assertEqual(1, len(order.items))
110
+ self.assertEqual("Amazon eGift Card - Birthday Candles (Animated)",
111
+ order.items[0].title)
112
+ self.assertIsNotNone(order.items[0].link)
113
+ self.assertIsNotNone(order.items[0].image_link)
114
+
80
115
  @responses.activate
81
116
  def test_get_order_history_paginated(self):
82
117
  # GIVEN
@@ -84,7 +119,7 @@ class TestOrders(UnitTestCase):
84
119
  year = 2010
85
120
  resp1 = self.given_order_history_landing_exists()
86
121
  resp2 = self.given_order_history_exists(year, 0)
87
- with open(os.path.join(self.RESOURCES_DIR, f"order-history-{year}-10.html"), "r",
122
+ with open(os.path.join(self.RESOURCES_DIR, "orders", f"order-history-{year}-10.html"), "r",
88
123
  encoding="utf-8") as f:
89
124
  resp3 = responses.add(
90
125
  responses.GET,
@@ -110,7 +145,7 @@ class TestOrders(UnitTestCase):
110
145
  year = 2024
111
146
  start_index = 0
112
147
  resp1 = self.given_order_history_landing_exists()
113
- with open(os.path.join(self.RESOURCES_DIR, "order-history-fresh.html"), "r",
148
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-fresh.html"), "r",
114
149
  encoding="utf-8") as f:
115
150
  resp2 = responses.add(
116
151
  responses.GET,
@@ -134,7 +169,7 @@ class TestOrders(UnitTestCase):
134
169
  year = 2024
135
170
  start_index = 0
136
171
  resp1 = self.given_order_history_landing_exists()
137
- with open(os.path.join(self.RESOURCES_DIR, "order-history-wholefoods.html"), "r",
172
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-wholefoods.html"), "r",
138
173
  encoding="utf-8") as f:
139
174
  resp2 = responses.add(
140
175
  responses.GET,
@@ -256,7 +291,7 @@ class TestOrders(UnitTestCase):
256
291
  # GIVEN
257
292
  self.amazon_session.is_authenticated = True
258
293
  order_id = "112-9685975-5907428"
259
- with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
294
+ with open(os.path.join(self.RESOURCES_DIR, "orders", f"order-details-{order_id}.html"), "r",
260
295
  encoding="utf-8") as f:
261
296
  resp1 = responses.add(
262
297
  responses.GET,
@@ -277,7 +312,7 @@ class TestOrders(UnitTestCase):
277
312
  # GIVEN
278
313
  self.amazon_session.is_authenticated = True
279
314
  order_id = "112-5939971-8962610"
280
- with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
315
+ with open(os.path.join(self.RESOURCES_DIR, "orders", f"order-details-{order_id}.html"), "r",
281
316
  encoding="utf-8") as f:
282
317
  resp1 = responses.add(
283
318
  responses.GET,
@@ -298,7 +333,7 @@ class TestOrders(UnitTestCase):
298
333
  # GIVEN
299
334
  self.amazon_session.is_authenticated = True
300
335
  order_id = "112-4482432-2955442"
301
- with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
336
+ with open(os.path.join(self.RESOURCES_DIR, "orders", f"order-details-{order_id}.html"), "r",
302
337
  encoding="utf-8") as f:
303
338
  resp1 = responses.add(
304
339
  responses.GET,
@@ -319,7 +354,7 @@ class TestOrders(UnitTestCase):
319
354
  # GIVEN
320
355
  self.amazon_session.is_authenticated = True
321
356
  order_id = "112-9087159-1657009"
322
- with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
357
+ with open(os.path.join(self.RESOURCES_DIR, "orders", f"order-details-{order_id}.html"), "r",
323
358
  encoding="utf-8") as f:
324
359
  resp1 = responses.add(
325
360
  responses.GET,
@@ -340,7 +375,7 @@ class TestOrders(UnitTestCase):
340
375
  # GIVEN
341
376
  self.amazon_session.is_authenticated = True
342
377
  order_id = "114-8722141-6545058"
343
- with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
378
+ with open(os.path.join(self.RESOURCES_DIR, "orders", f"order-details-{order_id}.html"), "r",
344
379
  encoding="utf-8") as f:
345
380
  resp1 = responses.add(
346
381
  responses.GET,
@@ -361,7 +396,7 @@ class TestOrders(UnitTestCase):
361
396
  # GIVEN
362
397
  self.amazon_session.is_authenticated = True
363
398
  order_id = "111-6778632-7354601"
364
- with open(os.path.join(self.RESOURCES_DIR, f"order-details-{order_id}.html"), "r",
399
+ with open(os.path.join(self.RESOURCES_DIR, "orders", f"order-details-{order_id}.html"), "r",
365
400
  encoding="utf-8") as f:
366
401
  resp1 = responses.add(
367
402
  responses.GET,
@@ -37,14 +37,15 @@ class TestSession(UnitTestCase):
37
37
  @responses.activate
38
38
  def test_login_invalid_username(self):
39
39
  # GIVEN
40
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
40
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
41
41
  resp1 = responses.add(
42
42
  responses.GET,
43
43
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
44
44
  body=f.read(),
45
45
  status=200,
46
46
  )
47
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-invalid-email.html"), "r", encoding="utf-8") as f:
47
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-invalid-email.html"), "r",
48
+ encoding="utf-8") as f:
48
49
  resp2 = responses.add(
49
50
  responses.POST,
50
51
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
@@ -64,14 +65,15 @@ class TestSession(UnitTestCase):
64
65
  @responses.activate
65
66
  def test_login_invalid_password(self):
66
67
  # GIVEN
67
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
68
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
68
69
  resp1 = responses.add(
69
70
  responses.GET,
70
71
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
71
72
  body=f.read(),
72
73
  status=200,
73
74
  )
74
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-invalid-password.html"), "r", encoding="utf-8") as f:
75
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-invalid-password.html"), "r",
76
+ encoding="utf-8") as f:
75
77
  resp2 = responses.add(
76
78
  responses.POST,
77
79
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
@@ -92,21 +94,21 @@ class TestSession(UnitTestCase):
92
94
  @patch("builtins.input")
93
95
  def test_mfa(self, input_mock):
94
96
  # GIVEN
95
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
97
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
96
98
  resp1 = responses.add(
97
99
  responses.GET,
98
100
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
99
101
  body=f.read(),
100
102
  status=200,
101
103
  )
102
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-mfa.html"), "r", encoding="utf-8") as f:
104
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-mfa.html"), "r", encoding="utf-8") as f:
103
105
  resp2 = responses.add(
104
106
  responses.POST,
105
107
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
106
108
  body=f.read(),
107
109
  status=200,
108
110
  )
109
- with open(os.path.join(self.RESOURCES_DIR, "order-history-2018-0.html"), "r", encoding="utf-8") as f:
111
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-2018-0.html"), "r", encoding="utf-8") as f:
110
112
  resp3 = responses.add(
111
113
  responses.POST,
112
114
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
@@ -128,28 +130,28 @@ class TestSession(UnitTestCase):
128
130
  @patch("builtins.input")
129
131
  def test_new_otp(self, input_mock):
130
132
  # GIVEN
131
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
133
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
132
134
  resp1 = responses.add(
133
135
  responses.GET,
134
136
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
135
137
  body=f.read(),
136
138
  status=200,
137
139
  )
138
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-new-otp.html"), "r", encoding="utf-8") as f:
140
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-new-otp.html"), "r", encoding="utf-8") as f:
139
141
  resp2 = responses.add(
140
142
  responses.POST,
141
143
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
142
144
  body=f.read(),
143
145
  status=200,
144
146
  )
145
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-mfa.html"), "r", encoding="utf-8") as f:
147
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-mfa.html"), "r", encoding="utf-8") as f:
146
148
  resp3 = responses.add(
147
149
  responses.POST,
148
150
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
149
151
  body=f.read(),
150
152
  status=200,
151
153
  )
152
- with open(os.path.join(self.RESOURCES_DIR, "order-history-2018-0.html"), "r", encoding="utf-8") as f:
154
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-2018-0.html"), "r", encoding="utf-8") as f:
153
155
  resp4 = responses.add(
154
156
  responses.POST,
155
157
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
@@ -171,7 +173,7 @@ class TestSession(UnitTestCase):
171
173
  @responses.activate
172
174
  def test_captcha_1(self):
173
175
  # GIVEN
174
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
176
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
175
177
  resp1 = responses.add(
176
178
  responses.GET,
177
179
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
@@ -184,14 +186,14 @@ class TestSession(UnitTestCase):
184
186
  status=302,
185
187
  headers={"Location": f"{self.test_config.constants.BASE_URL}/ap/cvf/request"}
186
188
  )
187
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-captcha-1.html"), "r", encoding="utf-8") as f:
189
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-captcha-1.html"), "r", encoding="utf-8") as f:
188
190
  resp3 = responses.add(
189
191
  responses.GET,
190
192
  f"{self.test_config.constants.BASE_URL}/ap/cvf/request",
191
193
  body=f.read(),
192
194
  status=200
193
195
  )
194
- with open(os.path.join(self.RESOURCES_DIR, "captcha_easy.jpg"), "rb") as f:
196
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "captcha_easy.jpg"), "rb") as f:
195
197
  resp4 = responses.add(
196
198
  responses.GET,
197
199
  "https://opfcaptcha-prod.s3.amazonaws.com/d32ff4fa043d4f969a1693adfb5d663a.jpg",
@@ -199,7 +201,7 @@ class TestSession(UnitTestCase):
199
201
  headers={"Content-Type": "image/jpeg"},
200
202
  status=200,
201
203
  )
202
- with open(os.path.join(self.RESOURCES_DIR, "order-history-2018-0.html"), "r", encoding="utf-8") as f:
204
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-2018-0.html"), "r", encoding="utf-8") as f:
203
205
  request_data = {
204
206
  "clientContext": "132-7968344-2156059",
205
207
  "cvf_captcha_captcha_action": "verifyCaptcha",
@@ -242,21 +244,21 @@ class TestSession(UnitTestCase):
242
244
  @responses.activate
243
245
  def test_captcha_2(self):
244
246
  # GIVEN
245
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
247
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
246
248
  resp1 = responses.add(
247
249
  responses.GET,
248
250
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
249
251
  body=f.read(),
250
252
  status=200,
251
253
  )
252
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-captcha-2.html"), "r", encoding="utf-8") as f:
254
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-captcha-2.html"), "r", encoding="utf-8") as f:
253
255
  resp2 = responses.add(
254
256
  responses.POST,
255
257
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
256
258
  body=f.read(),
257
259
  status=200,
258
260
  )
259
- with open(os.path.join(self.RESOURCES_DIR, "captcha_easy.jpg"), "rb") as f:
261
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "captcha_easy.jpg"), "rb") as f:
260
262
  resp3 = responses.add(
261
263
  responses.GET,
262
264
  "https://images-na.ssl-images-amazon.com/captcha/ddwwidnf/Captcha_gmwackhtzu.jpg",
@@ -264,7 +266,7 @@ class TestSession(UnitTestCase):
264
266
  headers={"Content-Type": "image/jpeg"},
265
267
  status=200,
266
268
  )
267
- with open(os.path.join(self.RESOURCES_DIR, "order-history-2018-0.html"), "r", encoding="utf-8") as f:
269
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-2018-0.html"), "r", encoding="utf-8") as f:
268
270
  resp4 = responses.add(
269
271
  responses.GET,
270
272
  f"{self.test_config.constants.BASE_URL}/errors/validateCaptcha",
@@ -293,7 +295,7 @@ class TestSession(UnitTestCase):
293
295
  @patch("PIL.Image.Image.show")
294
296
  def test_captcha_1_hard(self, show_mock, input_mock):
295
297
  # GIVEN
296
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
298
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
297
299
  resp1 = responses.add(
298
300
  responses.GET,
299
301
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
@@ -306,14 +308,14 @@ class TestSession(UnitTestCase):
306
308
  status=302,
307
309
  headers={"Location": f"{self.test_config.constants.BASE_URL}/ap/cvf/request"}
308
310
  )
309
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-captcha-1.html"), "r", encoding="utf-8") as f:
311
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-captcha-1.html"), "r", encoding="utf-8") as f:
310
312
  resp3 = responses.add(
311
313
  responses.GET,
312
314
  f"{self.test_config.constants.BASE_URL}/ap/cvf/request",
313
315
  body=f.read(),
314
316
  status=200
315
317
  )
316
- with open(os.path.join(self.RESOURCES_DIR, "captcha_hard.jpg"), "rb") as f:
318
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "captcha_hard.jpg"), "rb") as f:
317
319
  resp4 = responses.add(
318
320
  responses.GET,
319
321
  "https://opfcaptcha-prod.s3.amazonaws.com/d32ff4fa043d4f969a1693adfb5d663a.jpg",
@@ -321,7 +323,7 @@ class TestSession(UnitTestCase):
321
323
  headers={"Content-Type": "image/jpeg"},
322
324
  status=200,
323
325
  )
324
- with open(os.path.join(self.RESOURCES_DIR, "order-history-2018-0.html"), "r", encoding="utf-8") as f:
326
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-2018-0.html"), "r", encoding="utf-8") as f:
325
327
  resp5 = responses.add(
326
328
  responses.POST,
327
329
  f"{self.test_config.constants.BASE_URL}/ap/cvf/verify",
@@ -345,21 +347,23 @@ class TestSession(UnitTestCase):
345
347
  @patch("builtins.input")
346
348
  def test_captcha_otp(self, input_mock):
347
349
  # GIVEN
348
- with open(os.path.join(self.RESOURCES_DIR, "signin.html"), "r", encoding="utf-8") as f:
350
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "signin.html"), "r", encoding="utf-8") as f:
349
351
  resp1 = responses.add(
350
352
  responses.GET,
351
353
  f"{self.test_config.constants.BASE_URL}/gp/sign-in.html",
352
354
  body=f.read(),
353
355
  status=200,
354
356
  )
355
- with open(os.path.join(self.RESOURCES_DIR, "post-signin-captcha-otp.html"), "r", encoding="utf-8") as f:
357
+ with open(os.path.join(self.RESOURCES_DIR, "auth", "post-signin-captcha-otp.html"),
358
+ "r", encoding="utf-8") as f:
356
359
  resp2 = responses.add(
357
360
  responses.POST,
358
361
  self.test_config.constants.SIGN_IN_REDIRECT_URL,
359
362
  body=f.read(),
360
363
  status=200,
361
364
  )
362
- with open(os.path.join(self.RESOURCES_DIR, "order-history-2018-0.html"), "r", encoding="utf-8") as f:
365
+ with open(os.path.join(self.RESOURCES_DIR, "orders", "order-history-2018-0.html"),
366
+ "r", encoding="utf-8") as f:
363
367
  resp3 = responses.add(
364
368
  responses.POST,
365
369
  f"{self.test_config.constants.BASE_URL}/ap/cvf/approval/verifyOtp",
@@ -0,0 +1,137 @@
1
+ __copyright__ = "Copyright (c) 2024 Alex Laird"
2
+ __license__ = "MIT"
3
+
4
+ import datetime
5
+ import os
6
+ from unittest.mock import Mock, patch
7
+
8
+ import responses
9
+ from bs4 import BeautifulSoup
10
+
11
+ from amazonorders.session import AmazonSession
12
+ from amazonorders.transactions import AmazonTransactions, _parse_transaction_form_tag
13
+ from tests.unittestcase import UnitTestCase
14
+
15
+
16
+ class TestOrders(UnitTestCase):
17
+ temp_order_history_file_path = os.path.join(
18
+ os.path.abspath(os.path.dirname(__file__)), "output", "temp-order-history.html"
19
+ )
20
+ temp_order_details_file_path = os.path.join(
21
+ os.path.abspath(os.path.dirname(__file__)), "output", "temp-order-details.html"
22
+ )
23
+
24
+ def setUp(self):
25
+ super().setUp()
26
+
27
+ self.amazon_session = AmazonSession(
28
+ "some-username", "some-password", config=self.test_config
29
+ )
30
+
31
+ self.amazon_transactions = AmazonTransactions(self.amazon_session)
32
+
33
+ @responses.activate
34
+ @patch("amazonorders.transactions.datetime", wraps=datetime)
35
+ def test_get_transactions(self, mock_get_today: Mock):
36
+ # GIVEN
37
+ mock_get_today.date.today.return_value = datetime.date(2024, 10, 11)
38
+ days = 1
39
+ self.amazon_session.is_authenticated = True
40
+ with open(
41
+ os.path.join(self.RESOURCES_DIR, "transactions", "get-transactions.html"),
42
+ "r",
43
+ encoding="utf-8",
44
+ ) as f:
45
+ responses.add(
46
+ responses.GET,
47
+ f"{self.test_config.constants.TRANSACTION_HISTORY_LANDING_URL}",
48
+ body=f.read(),
49
+ status=200,
50
+ )
51
+
52
+ # WHEN
53
+ transactions = self.amazon_transactions.get_transactions(days=days)
54
+
55
+ # THEN
56
+ self.assertEqual(1, len(transactions))
57
+ transaction = transactions[0]
58
+ self.assertEqual(transaction.completed_date, datetime.date(2024, 10, 11))
59
+ self.assertEqual(transaction.payment_method, "Visa ****1234")
60
+ self.assertEqual(transaction.grand_total, -45.19)
61
+ self.assertFalse(transaction.is_refund)
62
+ self.assertEqual(transaction.order_number, "123-4567890-1234567")
63
+ self.assertEqual(transaction.order_details_link,
64
+ "https://www.amazon.ca/gp/css/summary/edit.html?orderID=123-4567890-1234567")
65
+ self.assertEqual(transaction.seller, "AMZN Mktp CA")
66
+
67
+ @responses.activate
68
+ @patch("amazonorders.transactions.datetime", wraps=datetime)
69
+ def test_get_transactions_with_pending(self, mock_get_today: Mock):
70
+ # GIVEN
71
+ mock_get_today.date.today.return_value = datetime.date(2025, 2, 13)
72
+ days = 30
73
+ self.amazon_session.is_authenticated = True
74
+ with open(
75
+ os.path.join(self.RESOURCES_DIR, "transactions", "transactions-in-progress.html"),
76
+ "r",
77
+ encoding="utf-8",
78
+ ) as f:
79
+ responses.add(
80
+ responses.GET,
81
+ f"{self.test_config.constants.TRANSACTION_HISTORY_LANDING_URL}",
82
+ body=f.read(),
83
+ status=200,
84
+ )
85
+
86
+ # WHEN
87
+ transactions = self.amazon_transactions.get_transactions(days=days)
88
+
89
+ # THEN
90
+ self.assertEqual(20, len(transactions))
91
+ transaction = transactions[0]
92
+ self.assertEqual(transaction.completed_date, datetime.date(2025, 2, 12))
93
+ self.assertEqual(transaction.payment_method, "Prime Visa ****1111")
94
+ self.assertEqual(transaction.grand_total, -26.29)
95
+ self.assertFalse(transaction.is_refund)
96
+ self.assertEqual(transaction.order_number, "234-8832881-7100260")
97
+ self.assertEqual(transaction.order_details_link,
98
+ "https://www.amazon.com/gp/your-account/order-details?orderID=234-8832881-7100260")
99
+ self.assertEqual(transaction.seller, None)
100
+ transaction = transactions[1]
101
+ self.assertEqual(transaction.completed_date, datetime.date(2025, 2, 7))
102
+ self.assertEqual(transaction.payment_method, "Prime Visa ****1111")
103
+ self.assertEqual(transaction.grand_total, 43.94)
104
+ self.assertTrue(transaction.is_refund)
105
+ self.assertEqual(transaction.order_number, "234-3017692-4601031")
106
+ self.assertEqual(transaction.order_details_link,
107
+ "https://www.amazon.com/gp/css/summary/edit.html?orderID=234-3017692-4601031")
108
+ self.assertEqual(transaction.seller, "AMZN Mktp US")
109
+
110
+ def test_parse_transaction_form_tag(self):
111
+ # GIVEN
112
+ with open(
113
+ os.path.join(self.RESOURCES_DIR, "transactions", "transaction-form-tag.html"),
114
+ "r",
115
+ encoding="utf-8",
116
+ ) as f:
117
+ parsed = BeautifulSoup(f.read(), self.test_config.bs4_parser)
118
+ form_tag = parsed.select_one("form")
119
+
120
+ # WHEN
121
+ transactions, next_page_url, next_page_data = _parse_transaction_form_tag(
122
+ form_tag, self.test_config
123
+ )
124
+
125
+ # THEN
126
+ self.assertEqual(len(transactions), 2)
127
+ self.assertEqual(
128
+ next_page_url, "https://www.amazon.com:443/cpe/yourpayments/transactions"
129
+ )
130
+ self.assertEqual(
131
+ next_page_data,
132
+ {
133
+ "ppw-widgetState": "the-ppw-widgetState",
134
+ "ie": "UTF-8",
135
+ 'ppw-widgetEvent:DefaultNextPageNavigationEvent:{"nextPageKey":"key"}': "",
136
+ },
137
+ )
@@ -1,4 +1,4 @@
1
- __copyright__ = "Copyright (c) 2024 Jeff Sawatzky"
1
+ __copyright__ = "Copyright (c) 2024 Alex Laird"
2
2
  __license__ = "MIT"
3
3
 
4
4
  from amazonorders.util import to_type
@@ -1,166 +0,0 @@
1
- __copyright__ = "Copyright (c) 2024 Jeff Sawatzky"
2
- __license__ = "MIT"
3
-
4
- import datetime
5
- import os
6
- from unittest.mock import Mock, patch
7
-
8
- import responses
9
- from bs4 import BeautifulSoup
10
-
11
- from amazonorders.session import AmazonSession
12
- from amazonorders.transactions import AmazonTransactions, _parse_transaction_form_tag
13
- from tests.unittestcase import UnitTestCase
14
-
15
-
16
- class TestOrders(UnitTestCase):
17
- temp_order_history_file_path = os.path.join(
18
- os.path.abspath(os.path.dirname(__file__)), "output", "temp-order-history.html"
19
- )
20
- temp_order_details_file_path = os.path.join(
21
- os.path.abspath(os.path.dirname(__file__)), "output", "temp-order-details.html"
22
- )
23
-
24
- def setUp(self):
25
- super().setUp()
26
-
27
- self.amazon_session = AmazonSession(
28
- "some-username", "some-password", config=self.test_config
29
- )
30
-
31
- self.amazon_transactions = AmazonTransactions(self.amazon_session)
32
-
33
- @responses.activate
34
- @patch("amazonorders.transactions.datetime", wraps=datetime)
35
- def test_transactions_command(self, mock_get_today: Mock):
36
- # GIVEN
37
- mock_get_today.date.today.return_value = datetime.date(2024, 10, 11)
38
- days = 1
39
- self.amazon_session.is_authenticated = True
40
- with open(
41
- os.path.join(self.RESOURCES_DIR, "get-transactions.html"),
42
- "r",
43
- encoding="utf-8",
44
- ) as f:
45
- responses.add(
46
- responses.GET,
47
- f"{self.test_config.constants.TRANSACTION_HISTORY_LANDING_URL}",
48
- body=f.read(),
49
- status=200,
50
- )
51
-
52
- # WHEN
53
- transactions = self.amazon_transactions.get_transactions(days=days)
54
-
55
- # THEN
56
- self.assertEqual(1, len(transactions))
57
-
58
- def test_parse_transaction_form_tag(self):
59
- # GIVEN
60
- parsed = BeautifulSoup(TEST_PARSE_TRANSACTION_FORM_TAG_HTML, self.test_config.bs4_parser)
61
- form_tag = parsed.select_one("form")
62
-
63
- # WHEN
64
- transactions, next_page_url, next_page_data = _parse_transaction_form_tag(
65
- form_tag, self.test_config
66
- )
67
-
68
- # THEN
69
- self.assertEqual(len(transactions), 2)
70
- self.assertEqual(
71
- next_page_url, "https://www.amazon.com:443/cpe/yourpayments/transactions"
72
- )
73
- self.assertEqual(
74
- next_page_data,
75
- {
76
- "ppw-widgetState": "the-ppw-widgetState",
77
- "ie": "UTF-8",
78
- 'ppw-widgetEvent:DefaultNextPageNavigationEvent:{"nextPageKey":"key"}': "",
79
- },
80
- )
81
-
82
-
83
- TEST_PARSE_TRANSACTION_FORM_TAG_HTML = """
84
- <form action="https://www.amazon.com:443/cpe/yourpayments/transactions" class="a-spacing-none" method="post"><input
85
- name="ppw-widgetState" type="hidden" value="the-ppw-widgetState" /><input name="ie" type="hidden"
86
- value="UTF-8" />
87
- <div class="a-box-group a-spacing-base">
88
- <div class="a-box a-spacing-none a-box-title apx-transactions-sleeve-header-container">
89
- <div class="a-box-inner a-padding-base"><span class="a-size-base a-text-bold">Completed</span></div>
90
- </div>
91
- <div class="a-box a-spacing-base">
92
- <div class="a-box-inner a-padding-none">
93
- <div class="a-section a-spacing-base a-padding-base apx-transaction-date-container pmts-portal-component pmts-portal-components-pp-kXMaEm-3"
94
- data-pmts-component-id="pp-kXMaEm-3"><span>October 11, 2024</span></div>
95
- <div class="a-section a-spacing-base pmts-portal-component pmts-portal-components-pp-kXMaEm-3"
96
- data-pmts-component-id="pp-kXMaEm-3">
97
- <div class="a-section a-spacing-base apx-transactions-line-item-component-container">
98
- <div class="a-row pmts-portal-component pmts-portal-components-pp-kXMaEm-4"
99
- data-pmts-component-id="pp-kXMaEm-4">
100
- <div class="a-column a-span9"><span class="a-size-base a-text-bold">Visa ****1234</span>
101
- </div>
102
- <div class="a-column a-span3 a-text-right a-span-last"><span
103
- class="a-size-base-plus a-text-bold">-CA$45.19</span></div>
104
- </div>
105
- <div class="a-section a-spacing-none a-spacing-top-mini pmts-portal-component pmts-portal-components-pp-kXMaEm-4"
106
- data-pmts-component-id="pp-kXMaEm-4">
107
- <div class="a-row">
108
- <div class="a-column a-span12"><a class="a-link-normal"
109
- href="https://www.amazon.ca/gp/css/summary/edit.html?orderID=123-4567890-1234567"
110
- id="pp-kXMaEm-50">Order #123-4567890-1234567</a></div>
111
- </div>
112
- </div>
113
- <div class="a-section a-spacing-none a-spacing-top-mini pmts-portal-component pmts-portal-components-pp-kXMaEm-4"
114
- data-pmts-component-id="pp-kXMaEm-4">
115
- <div class="a-row">
116
- <div class="a-column a-span12"><span class="a-size-base">AMZN Mktp CA</span></div>
117
- </div>
118
- </div>
119
- </div>
120
- </div>
121
- <div class="a-section a-spacing-base a-padding-base apx-transaction-date-container pmts-portal-component pmts-portal-components-pp-kXMaEm-8"
122
- data-pmts-component-id="pp-kXMaEm-8"><span>October 9, 2024</span></div>
123
- <div class="a-section a-spacing-base pmts-portal-component pmts-portal-components-pp-kXMaEm-8"
124
- data-pmts-component-id="pp-kXMaEm-8">
125
- <div class="a-section a-spacing-base apx-transactions-line-item-component-container">
126
- <div class="a-row pmts-portal-component pmts-portal-components-pp-kXMaEm-9"
127
- data-pmts-component-id="pp-kXMaEm-9">
128
- <div class="a-column a-span9"><span class="a-size-base a-text-bold">Mastercard
129
- ****1234</span></div>
130
- <div class="a-column a-span3 a-text-right a-span-last"><span
131
- class="a-size-base-plus a-text-bold">-CA$28.79</span></div>
132
- </div>
133
- <div class="a-section a-spacing-none a-spacing-top-mini pmts-portal-component pmts-portal-components-pp-kXMaEm-9"
134
- data-pmts-component-id="pp-kXMaEm-9">
135
- <div class="a-row">
136
- <div class="a-column a-span12"><a class="a-link-normal"
137
- href="https://www.amazon.ca/gp/css/summary/edit.html?orderID=123-4567890-1234567"
138
- id="pp-kXMaEm-52">Order #123-4567890-1234567</a></div>
139
- </div>
140
- </div>
141
- <div class="a-section a-spacing-none a-spacing-top-mini pmts-portal-component pmts-portal-components-pp-kXMaEm-9"
142
- data-pmts-component-id="pp-kXMaEm-9">
143
- <div class="a-row">
144
- <div class="a-column a-span12"><span class="a-size-base">Amazon.ca</span></div>
145
- </div>
146
- </div>
147
- </div>
148
- </div>
149
- </div>
150
- </div>
151
- </div>
152
- <div class="a-row a-spacing-top-extra-large">
153
- <div class="a-column a-span2 a-text-center"><span class="a-button a-button-span12 a-button-base"><span
154
- class="a-button-inner"><input class="a-button-input"
155
- name='ppw-widgetEvent:DefaultPreviousPageNavigationEvent:{"previousPageKey":"key"}'
156
- type="submit" /><span aria-hidden="true" class="a-button-text">Previous
157
- Page</span></span></span></div>
158
- <div class="a-column a-span2 a-text-center a-span-last"><span
159
- class="a-button a-button-span12 a-button-base"><span class="a-button-inner"><input
160
- class="a-button-input"
161
- name='ppw-widgetEvent:DefaultNextPageNavigationEvent:{"nextPageKey":"key"}'
162
- type="submit" /><span aria-hidden="true" class="a-button-text">Next Page</span></span></span>
163
- </div>
164
- </div>
165
- </form>
166
- """ # noqa
File without changes
File without changes
File without changes
File without changes