Adds remaining files from parallel development: - Services: analysis, csv_reader, forecasting, normalizer, recurring - UI: recurring_view, settings_view, sidebar, themes (dark/light) - Tests: analysis, csv_reader, forecasting, import_categorize, normalizer, recurring, integration - App entry point (main.py) and CLAUDE.md 52 tests passing across all modules. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
# tests/services/test_analysis.py
|
|
import datetime
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session
|
|
|
|
from src.db import Base
|
|
from src.models import *
|
|
from src.seed import seed_categories
|
|
from src.services.analysis import AnalysisService
|
|
|
|
|
|
def make_session():
|
|
engine = create_engine("sqlite:///:memory:")
|
|
Base.metadata.create_all(engine)
|
|
return Session(engine)
|
|
|
|
|
|
def make_test_data(session):
|
|
seed_categories(session)
|
|
member = HouseholdMember(name="Andrew", relationship="self")
|
|
session.add(member)
|
|
session.flush()
|
|
account = Account(name="Chase", institution="Chase", account_type="credit", owner_id=member.id)
|
|
session.add(account)
|
|
session.flush()
|
|
groceries = session.query(Category).filter_by(name="Groceries").one()
|
|
dining = session.query(Category).filter_by(name="Dining Out").one()
|
|
|
|
txns = [
|
|
Transaction(date=datetime.date(2026, 1, 5), amount=-50.0, description="PUBLIX", account_id=account.id, category_id=groceries.id, tag="needs"),
|
|
Transaction(date=datetime.date(2026, 1, 12), amount=-30.0, description="ALDI", account_id=account.id, category_id=groceries.id, tag="needs"),
|
|
Transaction(date=datetime.date(2026, 1, 20), amount=-15.0, description="CHICK-FIL-A", account_id=account.id, category_id=dining.id, tag="wants"),
|
|
Transaction(date=datetime.date(2026, 2, 3), amount=-60.0, description="PUBLIX", account_id=account.id, category_id=groceries.id, tag="needs"),
|
|
Transaction(date=datetime.date(2026, 2, 7), amount=-25.0, description="KFC", account_id=account.id, category_id=dining.id, tag="wants"),
|
|
]
|
|
session.add_all(txns)
|
|
session.commit()
|
|
return account, member
|
|
|
|
|
|
def test_spending_by_month():
|
|
session = make_session()
|
|
make_test_data(session)
|
|
svc = AnalysisService(session)
|
|
result = svc.spending_by_period("month")
|
|
assert len(result) == 2
|
|
jan = [r for r in result if r["period"] == "2026-01"][0]
|
|
assert jan["total"] == -95.0
|
|
|
|
|
|
def test_spending_by_category():
|
|
session = make_session()
|
|
make_test_data(session)
|
|
svc = AnalysisService(session)
|
|
result = svc.spending_by_category(
|
|
start=datetime.date(2026, 1, 1),
|
|
end=datetime.date(2026, 2, 28),
|
|
)
|
|
groceries_row = [r for r in result if r["category"] == "Groceries"][0]
|
|
assert groceries_row["total"] == -140.0
|
|
|
|
|
|
def test_spending_by_tag():
|
|
session = make_session()
|
|
make_test_data(session)
|
|
svc = AnalysisService(session)
|
|
result = svc.spending_by_tag(
|
|
start=datetime.date(2026, 1, 1),
|
|
end=datetime.date(2026, 2, 28),
|
|
)
|
|
needs = [r for r in result if r["tag"] == "needs"][0]
|
|
wants = [r for r in result if r["tag"] == "wants"][0]
|
|
assert needs["total"] == -140.0
|
|
assert wants["total"] == -40.0
|
|
|
|
|
|
def test_spending_filtered_by_person():
|
|
session = make_session()
|
|
account, member = make_test_data(session)
|
|
txns = session.query(Transaction).all()
|
|
for t in txns:
|
|
t.attributed_to_id = member.id
|
|
session.commit()
|
|
|
|
svc = AnalysisService(session)
|
|
result = svc.spending_by_category(
|
|
start=datetime.date(2026, 1, 1),
|
|
end=datetime.date(2026, 2, 28),
|
|
person_id=member.id,
|
|
)
|
|
total = sum(r["total"] for r in result)
|
|
assert total == -180.0
|