ynab-amazon-categorizer 2.2.0__tar.gz → 2.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 (42) hide show
  1. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/CHANGELOG.md +14 -0
  2. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/PKG-INFO +2 -2
  3. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/pyproject.toml +1 -1
  4. ynab_amazon_categorizer-2.2.2/src/ynab_amazon_categorizer/_version.py +24 -0
  5. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/amazon_parser.py +32 -9
  6. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/PKG-INFO +2 -2
  7. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/requires.txt +1 -1
  8. ynab_amazon_categorizer-2.2.2/tests/test_amazon_parser.py +224 -0
  9. ynab_amazon_categorizer-2.2.0/src/ynab_amazon_categorizer/_version.py +0 -34
  10. ynab_amazon_categorizer-2.2.0/tests/test_amazon_parser.py +0 -108
  11. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.env.example +0 -0
  12. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.git_archival.txt +0 -0
  13. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.gitattributes +0 -0
  14. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.github/workflows/release.yml +0 -0
  15. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.gitignore +0 -0
  16. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.pre-commit-config.yaml +0 -0
  17. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/AGENTS.md +0 -0
  18. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/CLAUDE.md +0 -0
  19. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/DEVELOPMENT.md +0 -0
  20. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/LICENSE +0 -0
  21. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/README.md +0 -0
  22. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/setup.cfg +0 -0
  23. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/__init__.py +0 -0
  24. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/__main__.py +0 -0
  25. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/cli.py +0 -0
  26. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/config.py +0 -0
  27. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/exceptions.py +0 -0
  28. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/memo_generator.py +0 -0
  29. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/transaction_matcher.py +0 -0
  30. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/ynab_client.py +0 -0
  31. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/SOURCES.txt +0 -0
  32. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/dependency_links.txt +0 -0
  33. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/entry_points.txt +0 -0
  34. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/top_level.txt +0 -0
  35. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/__init__.py +0 -0
  36. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_amazon_parser_real.py +0 -0
  37. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_cli_functions.py +0 -0
  38. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_config.py +0 -0
  39. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_memo_generator.py +0 -0
  40. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_transaction_matcher.py +0 -0
  41. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_ynab_client.py +0 -0
  42. {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/uv.lock +0 -0
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.2.2] - 2026-04-23
11
+
12
+ ### Fixed
13
+
14
+ - Pin `ty==0.0.14` in dev dependencies so CI uses the same type-checker version as the lock file
15
+
16
+ ## [2.2.1] - 2026-04-23
17
+
18
+ ### Fixed
19
+
20
+ - Cancelled orders no longer cause their items to bleed into the preceding valid order
21
+ - Quantity badge numbers (e.g. trailing ` 2`, ` 4`) are stripped from item names when the bare form also appears, without corrupting size numbers like `Size 10`
22
+ - `"Now arriving today X:XX p.m."` delivery status lines are no longer extracted as item names
23
+
10
24
  ## [2.2.0] - 2026-02-23
11
25
 
12
26
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ynab-amazon-categorizer
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Automatically categorize Amazon transactions in YNAB with rich item information
5
5
  Author: dizzlkheinz
6
6
  License: GPL-3.0
@@ -28,7 +28,7 @@ Provides-Extra: dev
28
28
  Requires-Dist: pytest>=9.0.2; extra == "dev"
29
29
  Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
30
30
  Requires-Dist: ruff>=0.15.0; extra == "dev"
31
- Requires-Dist: ty>=0.0.14; extra == "dev"
31
+ Requires-Dist: ty==0.0.14; extra == "dev"
32
32
  Requires-Dist: pre-commit>=4.5.1; extra == "dev"
33
33
  Dynamic: license-file
34
34
 
@@ -36,7 +36,7 @@ dev = [
36
36
  "pytest>=9.0.2",
37
37
  "pytest-cov>=7.0.0",
38
38
  "ruff>=0.15.0",
39
- "ty>=0.0.14",
39
+ "ty==0.0.14",
40
40
  "pre-commit>=4.5.1",
41
41
  ]
42
42
 
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '2.2.2'
22
+ __version_tuple__ = version_tuple = (2, 2, 2)
23
+
24
+ __commit_id__ = commit_id = 'g993627dd7'
@@ -22,6 +22,19 @@ class Order:
22
22
  class AmazonParser:
23
23
  """Parses Amazon order data from order history pages."""
24
24
 
25
+ def _remove_cancelled_orders(self, text: str) -> str:
26
+ """Remove cancelled order blocks so their items don't bleed into adjacent orders."""
27
+ parts = re.split(r"(?=Order placed)", text)
28
+ kept = []
29
+ for part in parts:
30
+ if (
31
+ re.match(r"\s*Order placed", part)
32
+ and "Your order was cancelled" in part
33
+ ):
34
+ continue
35
+ kept.append(part)
36
+ return "".join(kept)
37
+
25
38
  def parse_orders_page(self, orders_text: str) -> list[Order]:
26
39
  """Parse Amazon orders page text to extract order information.
27
40
 
@@ -31,6 +44,8 @@ class AmazonParser:
31
44
  if not orders_text.strip():
32
45
  return []
33
46
 
47
+ orders_text = self._remove_cancelled_orders(orders_text)
48
+
34
49
  orders = []
35
50
 
36
51
  # Find all order blocks using regex
@@ -76,7 +91,7 @@ class AmazonParser:
76
91
 
77
92
  def extract_items_from_content(self, order_content: str) -> list[str]:
78
93
  """Extract item names from order content."""
79
- items = []
94
+ candidates: list[str] = []
80
95
  lines = order_content.split("\n")
81
96
 
82
97
  for line in lines:
@@ -84,10 +99,10 @@ class AmazonParser:
84
99
  if not line or len(line) < 15:
85
100
  continue
86
101
 
87
- # Skip common UI elements
102
+ # Skip common UI elements and delivery status lines
88
103
  skip_patterns = [
89
104
  r"^(Buy it again|Track package|View|Return|Write|Get|Share|Leave|Ask)",
90
- r"^(Delivered|Arriving|Auto-delivered|Package was)",
105
+ r"^(Delivered|Arriving|Now arriving|Auto-delivered|Package was)",
91
106
  r"^(Return items:|Return or replace)",
92
107
  r"^\d+\.?\d* out of \d+ stars",
93
108
  r"^FREE|^Today by|^Get it|^List:|^Was:|^Limited-time deal",
@@ -139,15 +154,23 @@ class AmazonParser:
139
154
  "shipping",
140
155
  ]
141
156
  if not any(word in cleaned_line.lower() for word in skip_words):
142
- items.append(cleaned_line)
157
+ candidates.append(cleaned_line)
158
+
159
+ # Build lookup set to detect quantity-badge duplicates.
160
+ # Amazon shows "Product Name <qty>" and "Product Name" on adjacent lines when
161
+ # qty > 1. We want to strip the badge only when the bare form also appears.
162
+ candidate_set = set(candidates)
143
163
 
144
- # Remove duplicates, keep up to MAX_ITEMS_PER_ORDER
145
164
  seen: set[str] = set()
146
165
  unique_items: list[str] = []
147
- for item in items:
148
- if item not in seen and len(item) > 15: # Only keep substantial items
149
- seen.add(item)
150
- unique_items.append(item)
166
+ for item in candidates:
167
+ stripped = re.sub(r"\s+\d+$", "", item)
168
+ normalized = (
169
+ stripped if (stripped != item and stripped in candidate_set) else item
170
+ )
171
+ if normalized not in seen and len(normalized) > 15:
172
+ seen.add(normalized)
173
+ unique_items.append(normalized)
151
174
  if len(unique_items) >= MAX_ITEMS_PER_ORDER:
152
175
  break
153
176
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ynab-amazon-categorizer
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Automatically categorize Amazon transactions in YNAB with rich item information
5
5
  Author: dizzlkheinz
6
6
  License: GPL-3.0
@@ -28,7 +28,7 @@ Provides-Extra: dev
28
28
  Requires-Dist: pytest>=9.0.2; extra == "dev"
29
29
  Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
30
30
  Requires-Dist: ruff>=0.15.0; extra == "dev"
31
- Requires-Dist: ty>=0.0.14; extra == "dev"
31
+ Requires-Dist: ty==0.0.14; extra == "dev"
32
32
  Requires-Dist: pre-commit>=4.5.1; extra == "dev"
33
33
  Dynamic: license-file
34
34
 
@@ -6,5 +6,5 @@ python-dotenv>=1.2.1
6
6
  pytest>=9.0.2
7
7
  pytest-cov>=7.0.0
8
8
  ruff>=0.15.0
9
- ty>=0.0.14
9
+ ty==0.0.14
10
10
  pre-commit>=4.5.1
@@ -0,0 +1,224 @@
1
+ """Tests for Amazon order parsing functionality."""
2
+
3
+ from ynab_amazon_categorizer.amazon_parser import AmazonParser
4
+
5
+
6
+ def test_parse_simple_order() -> None:
7
+ """Test parsing a simple Amazon order."""
8
+ order_text = """
9
+ Order placed July 31, 2024
10
+ Total $57.57
11
+ Order # 702-8237239-1234567
12
+ Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
13
+ """
14
+
15
+ parser = AmazonParser()
16
+ orders = parser.parse_orders_page(order_text)
17
+
18
+ assert len(orders) == 1
19
+ order = orders[0]
20
+ assert order.order_id == "702-8237239-1234567"
21
+ assert order.total == 57.57
22
+ assert order.date_str == "July 31, 2024"
23
+ assert len(order.items) == 1
24
+ assert "Fancy Feast" in order.items[0]
25
+
26
+
27
+ def test_parse_empty_order_text() -> None:
28
+ """Test parsing empty order text returns empty list."""
29
+ parser = AmazonParser()
30
+ orders = parser.parse_orders_page("")
31
+ assert len(orders) == 0
32
+
33
+
34
+ def test_extract_items_from_order_content() -> None:
35
+ """Test extracting items from Amazon order content."""
36
+ parser = AmazonParser()
37
+
38
+ order_content = """
39
+ Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
40
+ ACME Brand Premium Dog Treats - Large Size 2 lb bag
41
+ Skip this line
42
+ Buy it again - should be skipped
43
+ Another product: Organic Cat Litter - 20 lbs Natural Clay
44
+ Track package - should be skipped
45
+ """
46
+
47
+ items = parser.extract_items_from_content(order_content)
48
+
49
+ assert len(items) >= 1
50
+ assert "Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)" in items
51
+
52
+
53
+ def test_parse_order_without_items_kept() -> None:
54
+ """Orders without parseable items are still kept (partial orders)."""
55
+ order_text = """
56
+ Order placed January 5, 2025
57
+ Total $15.00
58
+ Order # 702-1111111-2222222
59
+ short
60
+ """
61
+ parser = AmazonParser()
62
+ orders = parser.parse_orders_page(order_text)
63
+
64
+ # Order is kept even without items
65
+ assert len(orders) == 1
66
+ assert orders[0].order_id == "702-1111111-2222222"
67
+ assert orders[0].total == 15.00
68
+ assert orders[0].items == []
69
+
70
+
71
+ def test_parse_multiple_orders() -> None:
72
+ """Multiple orders in same text are parsed correctly."""
73
+ order_text = """
74
+ Order placed July 31, 2024
75
+ Total $57.57
76
+ Order # 702-1111111-1111111
77
+ Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
78
+
79
+ Order placed August 15, 2024
80
+ Total $89.99
81
+ Order # 702-2222222-2222222
82
+ Wireless Bluetooth Headphones - Over-Ear Noise Cancelling
83
+ """
84
+
85
+ parser = AmazonParser()
86
+ orders = parser.parse_orders_page(order_text)
87
+
88
+ assert len(orders) == 2
89
+ assert orders[0].order_id == "702-1111111-1111111"
90
+ assert orders[0].total == 57.57
91
+ assert orders[1].order_id == "702-2222222-2222222"
92
+ assert orders[1].total == 89.99
93
+
94
+
95
+ def test_extract_more_than_three_items() -> None:
96
+ """More than 3 items can be extracted (old 3-item limit removed)."""
97
+ parser = AmazonParser()
98
+
99
+ order_content = """
100
+ Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
101
+ ACME Brand Premium Dog Treats - Large Size 2 lb bag
102
+ Another product: Organic Cat Litter - 20 lbs Natural Clay
103
+ Super Premium Fancy Dog Collar with LED - Size Medium
104
+ Deluxe Pet Bed Extra Large Orthopedic Memory Foam 36 inch
105
+ """
106
+
107
+ items = parser.extract_items_from_content(order_content)
108
+ assert len(items) >= 4 # Should extract more than 3 now
109
+
110
+
111
+ def test_cancelled_order_items_do_not_bleed() -> None:
112
+ """Items from a cancelled order must not appear under the preceding valid order."""
113
+ # Order 701-9742314-2110603 ($29.58 capsules) is followed by a cancelled order
114
+ # whose item (Altra sneaker) must not be attributed to the capsules order.
115
+ order_text = """Order placed
116
+ April 22, 2026
117
+ Total
118
+ $29.58
119
+ Ship to
120
+ Shelby and Kalman Sutker
121
+ Order # 701-9742314-2110603
122
+ View order details Invoice
123
+
124
+ Now arriving today 5:15 p.m. - 8:15 p.m.
125
+
126
+ Yogti Psyllium Husk Capsules, 300 Count 2
127
+ Yogti Psyllium Husk Capsules, 300 Count
128
+ Buy it again
129
+
130
+ Track package
131
+ Return or replace items
132
+ Write a product review
133
+
134
+ Order placed
135
+ April 22, 2026
136
+ Order # 701-0552104-1147435
137
+
138
+ Cancelled
139
+ Your order was cancelled. You have not been charged for this order.
140
+
141
+ Altra Men's Lone Peak 9 Trail Running Shoe, Tan, 13 Wide
142
+ Altra Men's Lone Peak 9 Trail Running Shoe, Tan, 13 Wide
143
+
144
+ Order placed
145
+ April 22, 2026
146
+ Total
147
+ $29.73
148
+ Ship to
149
+ Shelby and Kalman Sutker
150
+ Order # 701-5052171-6951446
151
+ View order details Invoice
152
+
153
+ TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub 4
154
+ TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub
155
+ Buy it again
156
+ View your item
157
+ """
158
+
159
+ parser = AmazonParser()
160
+ orders = parser.parse_orders_page(order_text)
161
+
162
+ # Cancelled order must be omitted entirely
163
+ assert len(orders) == 2
164
+ order_ids = [o.order_id for o in orders]
165
+ assert "701-0552104-1147435" not in order_ids
166
+
167
+ capsules_order = next(o for o in orders if o.order_id == "701-9742314-2110603")
168
+ item_text = " ".join(capsules_order.items)
169
+ assert "Altra" not in item_text, "Cancelled-order item bled into adjacent order"
170
+ assert "Psyllium" in item_text
171
+
172
+
173
+ def test_quantity_badge_stripped_from_item_name() -> None:
174
+ """Trailing quantity badge numbers are removed when the bare name also appears."""
175
+ parser = AmazonParser()
176
+
177
+ # Amazon shows "Product Name <qty>" then "Product Name" on successive lines.
178
+ order_content = """
179
+ TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub 4
180
+ TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub
181
+ Buy it again
182
+ View your item
183
+ Nizoral Anti-Dandruff Shampoo with 2% Ketoconazole, Fresh Scent, 325 ml
184
+ Nizoral Anti-Dandruff Shampoo with 2% Ketoconazole, Fresh Scent, 325 ml
185
+ """
186
+
187
+ items = parser.extract_items_from_content(order_content)
188
+ assert len(items) == 2
189
+ assert all("454g Tub" in i or "Ketoconazole" in i for i in items)
190
+ # Bare form preserved, badge number gone
191
+ assert not any(i.endswith(" 4") for i in items)
192
+
193
+
194
+ def test_size_number_not_stripped() -> None:
195
+ """Trailing size numbers (2+ digits) are NOT stripped — they are part of the name."""
196
+ parser = AmazonParser()
197
+
198
+ order_content = """
199
+ Corset Femme Tops Floral Renaissance Pirate Overbust Boned Bustier Tops Green Size 10
200
+ Corset Femme Tops Floral Renaissance Pirate Overbust Boned Bustier Tops Green Size 10
201
+ Return items: Eligible through May 17, 2026
202
+ Buy it again
203
+ """
204
+
205
+ items = parser.extract_items_from_content(order_content)
206
+ assert len(items) == 1
207
+ assert items[0].endswith("Size 10")
208
+
209
+
210
+ def test_now_arriving_status_line_skipped() -> None:
211
+ """'Now arriving today X:XX p.m.' delivery status must not be extracted as an item."""
212
+ parser = AmazonParser()
213
+
214
+ order_content = """
215
+ Now arriving today 5:15 p.m. - 8:15 p.m.
216
+
217
+ Yogti Psyllium Husk Capsules, 300 Count
218
+ Yogti Psyllium Husk Capsules, 300 Count
219
+ Buy it again
220
+ """
221
+
222
+ items = parser.extract_items_from_content(order_content)
223
+ assert all("arriving" not in i.lower() for i in items)
224
+ assert any("Psyllium" in i for i in items)
@@ -1,34 +0,0 @@
1
- # file generated by setuptools-scm
2
- # don't change, don't track in version control
3
-
4
- __all__ = [
5
- "__version__",
6
- "__version_tuple__",
7
- "version",
8
- "version_tuple",
9
- "__commit_id__",
10
- "commit_id",
11
- ]
12
-
13
- TYPE_CHECKING = False
14
- if TYPE_CHECKING:
15
- from typing import Tuple
16
- from typing import Union
17
-
18
- VERSION_TUPLE = Tuple[Union[int, str], ...]
19
- COMMIT_ID = Union[str, None]
20
- else:
21
- VERSION_TUPLE = object
22
- COMMIT_ID = object
23
-
24
- version: str
25
- __version__: str
26
- __version_tuple__: VERSION_TUPLE
27
- version_tuple: VERSION_TUPLE
28
- commit_id: COMMIT_ID
29
- __commit_id__: COMMIT_ID
30
-
31
- __version__ = version = '2.2.0'
32
- __version_tuple__ = version_tuple = (2, 2, 0)
33
-
34
- __commit_id__ = commit_id = 'g431256193'
@@ -1,108 +0,0 @@
1
- """Tests for Amazon order parsing functionality."""
2
-
3
- from ynab_amazon_categorizer.amazon_parser import AmazonParser
4
-
5
-
6
- def test_parse_simple_order() -> None:
7
- """Test parsing a simple Amazon order."""
8
- order_text = """
9
- Order placed July 31, 2024
10
- Total $57.57
11
- Order # 702-8237239-1234567
12
- Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
13
- """
14
-
15
- parser = AmazonParser()
16
- orders = parser.parse_orders_page(order_text)
17
-
18
- assert len(orders) == 1
19
- order = orders[0]
20
- assert order.order_id == "702-8237239-1234567"
21
- assert order.total == 57.57
22
- assert order.date_str == "July 31, 2024"
23
- assert len(order.items) == 1
24
- assert "Fancy Feast" in order.items[0]
25
-
26
-
27
- def test_parse_empty_order_text() -> None:
28
- """Test parsing empty order text returns empty list."""
29
- parser = AmazonParser()
30
- orders = parser.parse_orders_page("")
31
- assert len(orders) == 0
32
-
33
-
34
- def test_extract_items_from_order_content() -> None:
35
- """Test extracting items from Amazon order content."""
36
- parser = AmazonParser()
37
-
38
- order_content = """
39
- Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
40
- ACME Brand Premium Dog Treats - Large Size 2 lb bag
41
- Skip this line
42
- Buy it again - should be skipped
43
- Another product: Organic Cat Litter - 20 lbs Natural Clay
44
- Track package - should be skipped
45
- """
46
-
47
- items = parser.extract_items_from_content(order_content)
48
-
49
- assert len(items) >= 1
50
- assert "Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)" in items
51
-
52
-
53
- def test_parse_order_without_items_kept() -> None:
54
- """Orders without parseable items are still kept (partial orders)."""
55
- order_text = """
56
- Order placed January 5, 2025
57
- Total $15.00
58
- Order # 702-1111111-2222222
59
- short
60
- """
61
- parser = AmazonParser()
62
- orders = parser.parse_orders_page(order_text)
63
-
64
- # Order is kept even without items
65
- assert len(orders) == 1
66
- assert orders[0].order_id == "702-1111111-2222222"
67
- assert orders[0].total == 15.00
68
- assert orders[0].items == []
69
-
70
-
71
- def test_parse_multiple_orders() -> None:
72
- """Multiple orders in same text are parsed correctly."""
73
- order_text = """
74
- Order placed July 31, 2024
75
- Total $57.57
76
- Order # 702-1111111-1111111
77
- Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
78
-
79
- Order placed August 15, 2024
80
- Total $89.99
81
- Order # 702-2222222-2222222
82
- Wireless Bluetooth Headphones - Over-Ear Noise Cancelling
83
- """
84
-
85
- parser = AmazonParser()
86
- orders = parser.parse_orders_page(order_text)
87
-
88
- assert len(orders) == 2
89
- assert orders[0].order_id == "702-1111111-1111111"
90
- assert orders[0].total == 57.57
91
- assert orders[1].order_id == "702-2222222-2222222"
92
- assert orders[1].total == 89.99
93
-
94
-
95
- def test_extract_more_than_three_items() -> None:
96
- """More than 3 items can be extracted (old 3-item limit removed)."""
97
- parser = AmazonParser()
98
-
99
- order_content = """
100
- Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
101
- ACME Brand Premium Dog Treats - Large Size 2 lb bag
102
- Another product: Organic Cat Litter - 20 lbs Natural Clay
103
- Super Premium Fancy Dog Collar with LED - Size Medium
104
- Deluxe Pet Bed Extra Large Orthopedic Memory Foam 36 inch
105
- """
106
-
107
- items = parser.extract_items_from_content(order_content)
108
- assert len(items) >= 4 # Should extract more than 3 now