initial commit
This commit is contained in:
77
tests/test_ai.py
Normal file
77
tests/test_ai.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import ai
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_classify_live():
|
||||
item_names = [
|
||||
'Syston Cable Technology Cat 6A+ Ethernet Network Cable,700MHz 23AWG Solid Bare Copper Wire Outdoor/Indoor, No Ends 100/250/500/1000 Ft Available, Heat Resistant Plenum Rated (100 FT, CMP, Blue-CMP)',
|
||||
"Chef'n Salad Dressing Mixer, Baking White/Vintage Indigo",
|
||||
"Lee Women's Ultra Lux Comfort with Flex Motion Skinny Leg Jean with Ever Fit, Black",
|
||||
]
|
||||
|
||||
categories = [
|
||||
'Home: Mortgage',
|
||||
'Home: Property Tax',
|
||||
'Home: Home Insurance',
|
||||
'Home: Water',
|
||||
'Home: Oil',
|
||||
'Home: Other Utilities',
|
||||
'Car: Car Insurance - Annual',
|
||||
'Car: Car Insurance - Monthly',
|
||||
'Car: Maintenance',
|
||||
'Internal Master Category: Inflow: Ready to Assign',
|
||||
'Internal Master Category: Deferred Income SubCategory',
|
||||
'Internal Master Category: Uncategorized',
|
||||
'Essentials: Groceries',
|
||||
"Essentials: I'm Too Lazy To Cook",
|
||||
'Essentials: Gas',
|
||||
'Essentials: Clothes',
|
||||
'Essentials: Baby',
|
||||
'Essentials: Toiletries and Hygiene',
|
||||
'Essentials: Medical',
|
||||
'Essentials: Software Subscriptions',
|
||||
'Essentials: Stuff I Forgot to Budget For',
|
||||
'Essentials: Charity',
|
||||
'Semi-Essentials: Sports and Fitness',
|
||||
'Semi-Essentials: Gifts',
|
||||
'Semi-Essentials: Technology',
|
||||
'Semi-Essentials: Household Items',
|
||||
'Semi-Essentials: Travel',
|
||||
'Credit Card Payments: Citi Credit',
|
||||
'Credit Card Payments: BoA Credit',
|
||||
'Credit Card Payments: Amazon Credit',
|
||||
"Credit Card Payments: BJ's Credit",
|
||||
'Debt Payments: Student Loan',
|
||||
'Just for Fun: Date Night',
|
||||
'Just for Fun: Hobbies/Activities',
|
||||
'Just for Fun: Doing Things With Friends',
|
||||
'Just for Fun: Gaming',
|
||||
'Just for Fun: Media',
|
||||
'Long-term: Home Improvement',
|
||||
'Long-term: The Future'
|
||||
]
|
||||
|
||||
res = ai.classify(item_names, categories)
|
||||
assert len(res) == 3
|
||||
expected_categories = [
|
||||
'Semi-Essentials: Technology',
|
||||
'Semi-Essentials: Household Items',
|
||||
'Essentials: Clothes'
|
||||
]
|
||||
for classified, ec in zip(res.values(), expected_categories):
|
||||
assert classified.category == ec
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_classify_live_trouble():
|
||||
with open('output/item_names.json') as f:
|
||||
item_names = json.load(f)
|
||||
with open('output/text_categories.json') as f:
|
||||
text_categories = json.load(f)
|
||||
|
||||
res = ai.classify(item_names, text_categories)
|
||||
assert set(item_names) ^ res.keys() == set()
|
||||
66
tests/test_amazon.py
Normal file
66
tests/test_amazon.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import amazon
|
||||
|
||||
|
||||
def test_get_accounts():
|
||||
env = {
|
||||
'AMAZON_EMAIL': 'test@example.com',
|
||||
'AMAZON_PASSWORD': 'password123',
|
||||
}
|
||||
res = amazon.get_accounts(env)
|
||||
|
||||
assert len(res) == 1
|
||||
email, pwd = res[0]
|
||||
assert email == 'test@example.com'
|
||||
assert pwd == 'password123'
|
||||
|
||||
|
||||
def test_get_accounts_numbered():
|
||||
env = {
|
||||
'AMAZON_EMAIL_0': 'test@example.com',
|
||||
'AMAZON_PASSWORD_0': 'password123',
|
||||
}
|
||||
res = amazon.get_accounts(env)
|
||||
|
||||
assert len(res) == 1
|
||||
email, pwd = res[0]
|
||||
assert email == 'test@example.com'
|
||||
assert pwd == 'password123'
|
||||
|
||||
|
||||
def test_get_accounts_numbered_multi():
|
||||
env = {
|
||||
'AMAZON_EMAIL_0': 'test@example.com',
|
||||
'AMAZON_PASSWORD_0': 'password123',
|
||||
'AMAZON_EMAIL_1': 'test2@example.com',
|
||||
'AMAZON_PASSWORD_1': 'password456',
|
||||
}
|
||||
res = amazon.get_accounts(env)
|
||||
|
||||
assert len(res) == 2
|
||||
|
||||
email1, pwd1 = res[0]
|
||||
assert email1 == 'test@example.com'
|
||||
assert pwd1 == 'password123'
|
||||
|
||||
email2, pwd2 = res[1]
|
||||
assert email2 == 'test2@example.com'
|
||||
assert pwd2 == 'password456'
|
||||
|
||||
|
||||
def test_get_accounts_both():
|
||||
env = {
|
||||
'AMAZON_EMAIL': 'test@example.com',
|
||||
'AMAZON_PASSWORD': 'password123',
|
||||
'AMAZON_EMAIL_0': 'test2@example.com',
|
||||
'AMAZON_PASSWORD_0': 'password456',
|
||||
}
|
||||
res = amazon.get_accounts(env)
|
||||
|
||||
assert len(res) == 2
|
||||
email1, pwd1 = res[0]
|
||||
assert email1 == 'test@example.com'
|
||||
assert pwd1 == 'password123'
|
||||
|
||||
email2, pwd2 = res[1]
|
||||
assert email2 == 'test2@example.com'
|
||||
assert pwd2 == 'password456'
|
||||
206
tests/test_main.py
Normal file
206
tests/test_main.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import datetime
|
||||
import json
|
||||
import pickle
|
||||
from types import SimpleNamespace
|
||||
|
||||
from amazonorders.entity.order import Order
|
||||
from amazonorders.entity.item import Item
|
||||
from amazonorders.entity.shipment import Shipment
|
||||
from amazonorders.entity.transaction import Transaction
|
||||
from amazonorders.entity.recipient import Recipient
|
||||
import pytest
|
||||
|
||||
import main
|
||||
|
||||
|
||||
KEYS = {
|
||||
Item: [
|
||||
'title', 'link', 'price', 'seller', 'condition',
|
||||
'return_eligible_date', 'image_link', 'quantity'
|
||||
],
|
||||
Order: [
|
||||
'full_details', 'index', 'shipments', 'items', 'order_number', 'order_details_link',
|
||||
'grand_total', 'order_placed_date', 'recipient', 'payment_method', 'payment_method_last_4',
|
||||
'subtotal', 'shipping_total', 'free_shipping', 'promotion_applied', 'coupon_savings',
|
||||
'reward_points', 'subscription_discount', 'total_before_tax', 'estimated_tax',
|
||||
'refund_total', 'multibuy_discount', 'amazon_discount', 'gift_card', 'gift_wrap',
|
||||
],
|
||||
Transaction: [
|
||||
'completed_date', 'payment_method', 'grand_total', 'is_refund',
|
||||
'order_number', 'order_details_link', 'seller',
|
||||
],
|
||||
Shipment: [
|
||||
'items', 'delivery_status', 'tracking_link',
|
||||
],
|
||||
Recipient: [
|
||||
'name', 'address',
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def to_dict(entity):
|
||||
entity_keys = KEYS[type(entity)]
|
||||
result = {}
|
||||
for k, v in entity.__dict__.items():
|
||||
if k in entity_keys:
|
||||
if type(v) in KEYS:
|
||||
result[k] = to_dict(v)
|
||||
elif type(v) == list and len(v) > 0 and type(v[0]) in KEYS:
|
||||
result[k] = [to_dict(i) for i in v]
|
||||
elif type(v) in (datetime.date, datetime.datetime):
|
||||
result[k] = v.isoformat()
|
||||
else:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
def mock_amz_hist():
|
||||
with open('output/transactions_2025.pkl', 'rb') as f:
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
def mock_ynab_hist():
|
||||
with open('output/ynab_history.json') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def test_get_tx_categories(monkeypatch):
|
||||
order = SimpleNamespace(
|
||||
items=[
|
||||
SimpleNamespace(
|
||||
title='An example item',
|
||||
price=17.49
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
item_details={
|
||||
'An example item': SimpleNamespace(
|
||||
category='Example category',
|
||||
description='example',
|
||||
)
|
||||
}
|
||||
|
||||
def classify(_names, _categories):
|
||||
return item_details
|
||||
monkeypatch.setattr('ai.classify', classify)
|
||||
|
||||
res = main.get_tx_categories(order, item_details)
|
||||
assert len(res) == 1
|
||||
assert res[0].category_name == 'Example category'
|
||||
assert res[0].description == 'example'
|
||||
assert res[0].ratio == 1.0
|
||||
|
||||
|
||||
def test_get_categories_multi_items(monkeypatch):
|
||||
order = SimpleNamespace(
|
||||
items=[
|
||||
SimpleNamespace(
|
||||
title='An example item',
|
||||
price=17.49
|
||||
),
|
||||
SimpleNamespace(
|
||||
title='Another example',
|
||||
price=12.34,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
item_details={
|
||||
'An example item': SimpleNamespace(
|
||||
category='Example category',
|
||||
description='example',
|
||||
),
|
||||
'Another example': SimpleNamespace(
|
||||
category='Example category',
|
||||
description='second example',
|
||||
)
|
||||
}
|
||||
|
||||
def classify(_names, _categories):
|
||||
return item_details
|
||||
monkeypatch.setattr('ai.classify', classify)
|
||||
|
||||
res = main.get_tx_categories(order, item_details)
|
||||
assert len(res) == 1
|
||||
assert res[0].category_name == 'Example category'
|
||||
assert res[0].description == 'example, second example'
|
||||
assert res[0].ratio == 1.0
|
||||
|
||||
|
||||
def test_get_categories_multi_categories(monkeypatch):
|
||||
order = SimpleNamespace(
|
||||
items=[
|
||||
SimpleNamespace(
|
||||
title='An example item',
|
||||
price=17.49
|
||||
),
|
||||
SimpleNamespace(
|
||||
title='Another example',
|
||||
price=12.34,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
item_details={
|
||||
'An example item': SimpleNamespace(
|
||||
category='Example category',
|
||||
description='example',
|
||||
),
|
||||
'Another example': SimpleNamespace(
|
||||
category='Other category',
|
||||
description='second example',
|
||||
)
|
||||
}
|
||||
|
||||
def classify(_names, _categories):
|
||||
return item_details
|
||||
monkeypatch.setattr('ai.classify', classify)
|
||||
|
||||
res = main.get_tx_categories(order, item_details)
|
||||
assert len(res) == 2
|
||||
assert res[0].category_name == 'Example category'
|
||||
assert res[0].description == 'example'
|
||||
assert res[0].ratio == pytest.approx(0.59, abs=0.01)
|
||||
|
||||
assert res[1].category_name == 'Other category'
|
||||
assert res[1].description == 'second example'
|
||||
assert res[1].ratio == pytest.approx(0.41, abs=0.01)
|
||||
|
||||
|
||||
def test_build_tx_update_multi():
|
||||
tx_categories = [
|
||||
SimpleNamespace(
|
||||
category_name='Example category',
|
||||
description='example',
|
||||
ratio=0.68,
|
||||
),
|
||||
SimpleNamespace(
|
||||
category_name='Other category',
|
||||
description='second example',
|
||||
ratio=0.32,
|
||||
)
|
||||
]
|
||||
|
||||
category_ids = {
|
||||
'Example category': '123',
|
||||
'Other category': '456',
|
||||
}
|
||||
|
||||
# ynab uses tenths of a cent as its unit
|
||||
tx_total = 23760
|
||||
|
||||
res = main.build_tx_update(tx_categories, category_ids, tx_total)
|
||||
assert len(res.subtransactions) == 2
|
||||
|
||||
sub0 = res.subtransactions[0]
|
||||
assert sub0.category_id == '123'
|
||||
assert sub0.memo == 'example'
|
||||
assert sub0.amount == 16157
|
||||
|
||||
sub1 = res.subtransactions[1]
|
||||
assert sub1.category_id == '456'
|
||||
assert sub1.memo == 'second example'
|
||||
assert sub1.amount == 7603
|
||||
Reference in New Issue
Block a user