feat: add SQLAlchemy database models and tests

Add the data layer for the spending analysis app including models for
household members, accounts, categories, transactions, categorization
rules, and CSV import mappings. All models use SQLAlchemy 2.0 mapped
columns with proper foreign key relationships. Includes db.py with
Base class, engine/session factories, and 6 passing tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 14:38:35 -05:00
parent eb4bfda1db
commit 8360de71ef
9 changed files with 282 additions and 0 deletions

112
tests/models/test_models.py Normal file
View File

@@ -0,0 +1,112 @@
import datetime
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from src.db import Base
from src.models.household import HouseholdMember
from src.models.account import Account
from src.models.category import Category
from src.models.transaction import Transaction
from src.models.rule import CategorizationRule
from src.models.csv_mapping import CsvMapping
def make_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
return Session(engine)
def test_create_household_member():
session = make_session()
member = HouseholdMember(name="Andrew", relationship="self")
session.add(member)
session.commit()
assert member.id is not None
assert member.name == "Andrew"
def test_create_account_with_owner():
session = make_session()
member = HouseholdMember(name="Andrew", relationship="self")
session.add(member)
session.flush()
account = Account(
name="Chase Freedom",
institution="Chase",
account_type="credit",
owner_id=member.id,
)
session.add(account)
session.commit()
assert account.owner.name == "Andrew"
def test_create_category():
session = make_session()
cat = Category(name="Groceries", default_tag="needs")
session.add(cat)
session.commit()
assert cat.id is not None
def test_create_transaction():
session = make_session()
member = HouseholdMember(name="Andrew", relationship="self")
session.add(member)
session.flush()
account = Account(name="Checking", institution="Wells Fargo", account_type="checking", owner_id=member.id)
cat = Category(name="Groceries", default_tag="needs")
session.add_all([account, cat])
session.flush()
txn = Transaction(
date=datetime.date(2026, 1, 15),
amount=-48.52,
description="WAL-MART #7181",
raw_description="PURCHASE AUTHORIZED ON 01/14 WAL-MART #7181 BEAUFORT SC CARD 5360",
account_id=account.id,
category_id=cat.id,
attributed_to_id=member.id,
tag="needs",
)
session.add(txn)
session.commit()
assert txn.id is not None
assert txn.account.name == "Checking"
assert txn.category.name == "Groceries"
def test_create_categorization_rule():
session = make_session()
cat = Category(name="Groceries", default_tag="needs")
session.add(cat)
session.flush()
rule = CategorizationRule(
pattern="WAL-MART",
category_id=cat.id,
priority=10,
)
session.add(rule)
session.commit()
assert rule.id is not None
def test_create_csv_mapping():
session = make_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()
mapping = CsvMapping(
name="Chase Credit Card",
fingerprint="Transaction Date,Post Date,Description,Category,Type,Amount,Memo",
column_map='{"date": "Transaction Date", "amount": "Amount", "description": "Description"}',
amount_logic="signed",
account_id=account.id,
)
session.add(mapping)
session.commit()
assert mapping.id is not None