Ledgerly/tests/test_cli.py
2026-10-27 11:04:50 -05:00

236 lines
7.6 KiB
Python

"""End-to-end CLI tests: main() with a temp database, captured stdout/stderr."""
import pytest
from ledgerly import __version__
from ledgerly.cli import build_parser, default_db_path, main
from ledgerly.models import DEFAULT_CATEGORIES
@pytest.fixture()
def db(tmp_path):
return str(tmp_path / "test.db")
def run(db, *argv):
return main(list(argv), db_path=db)
# -- basics --------------------------------------------------------------------
def test_version(db, capsys):
with pytest.raises(SystemExit) as exc:
main(["--version"], db_path=db)
assert exc.value.code == 0
assert __version__ in capsys.readouterr().out
def test_missing_command(db):
with pytest.raises(SystemExit) as exc:
main([], db_path=db)
assert exc.value.code == 2
def test_default_db_path_under_home():
path = default_db_path()
assert path.name == "ledgerly.db"
assert ".ledgerly" in path.parts
def test_parser_exposes_all_commands():
parser = build_parser()
assert parser.prog == "ledgerly"
# -- add / list / delete ---------------------------------------------------------
def test_add_and_list(db, capsys):
assert run(db, "add", "82.10", "--category", "Groceries",
"--date", "2026-10-02", "--note", "weekly") == 0
out = capsys.readouterr().out
assert "#1" in out and "$82.10" in out and "weekly" in out
assert run(db, "list", "--month", "2026-10") == 0
assert "Groceries" in capsys.readouterr().out
def test_add_default_date_and_month(db, capsys):
assert run(db, "add", "10.00", "--category", "Rent") == 0
assert run(db, "list") == 0 # current month by default
assert "Rent" in capsys.readouterr().out
def test_add_invalid_amount(db):
with pytest.raises(SystemExit) as exc:
run(db, "add", "abc", "--category", "Rent")
assert exc.value.code == 2
def test_add_invalid_date(db):
with pytest.raises(SystemExit) as exc:
run(db, "add", "1.00", "--category", "Rent", "--date", "nope")
assert exc.value.code == 2
def test_add_unknown_category_domain_error(db, capsys):
assert run(db, "add", "1.00", "--category", "Nope") == 1
assert "unknown category" in capsys.readouterr().err
def test_add_zero_amount(db, capsys):
assert run(db, "add", "0", "--category", "Rent") == 1
assert "greater than zero" in capsys.readouterr().err
def test_list_all(db, capsys):
run(db, "add", "5.00", "--category", "Rent", "--date", "2025-01-05")
run(db, "add", "6.00", "--category", "Rent", "--date", "2026-12-01")
assert run(db, "list", "--all") == 0
out = capsys.readouterr().out
assert "2025-01-05" in out and "2026-12-01" in out
def test_list_no_transactions(db, capsys):
assert run(db, "list", "--month", "2001-01") == 0
assert "no transactions" in capsys.readouterr().out
def test_list_by_category(db, capsys):
run(db, "add", "5.00", "--category", "Rent", "--date", "2026-10-05")
run(db, "add", "6.00", "--category", "Groceries", "--date", "2026-10-06")
capsys.readouterr() # discard add output; keep the list output clean
assert run(db, "list", "--month", "2026-10", "--category", "rent") == 0
out = capsys.readouterr().out
assert "Rent" in out and "Groceries" not in out
def test_delete(db, capsys):
assert run(db, "add", "5.00", "--category", "Rent", "--date", "2026-01-05") == 0
out = capsys.readouterr().out
tx_id = int(out.split("#", 1)[1].split(":", 1)[0].strip())
assert run(db, "delete", str(tx_id)) == 0
assert "deleted" in capsys.readouterr().out
def test_delete_unknown(db, capsys):
assert run(db, "delete", "999") == 1
assert "unknown transaction" in capsys.readouterr().err
# -- categories ------------------------------------------------------------------
def test_categories(db, capsys):
assert run(db, "categories") == 0
out = capsys.readouterr().out
assert "Salary" in out and "Rent" in out
def test_categories_empty(db, capsys):
for name, _kind in DEFAULT_CATEGORIES:
assert run(db, "category-del", name) == 0
assert run(db, "categories") == 0
assert "no categories" in capsys.readouterr().out
def test_category_add_and_del(db, capsys):
assert run(db, "category-add", "Pets", "--type", "expense") == 0
assert "Pets" in capsys.readouterr().out
assert run(db, "category-add", "pets", "--type", "income") == 1
assert "already exists" in capsys.readouterr().err
assert run(db, "category-del", "Pets") == 0
assert "deleted category" in capsys.readouterr().out
def test_category_add_bad_type(db):
with pytest.raises(SystemExit) as exc:
run(db, "category-add", "Pets", "--type", "asset")
assert exc.value.code == 2
def test_category_del_in_use(db, capsys):
run(db, "add", "5.00", "--category", "Rent", "--date", "2026-01-05")
assert run(db, "category-del", "Rent") == 1
assert "transactions or a budget" in capsys.readouterr().err
# -- budgets -----------------------------------------------------------------------
def test_budget_set_and_del(db, capsys):
assert run(db, "budget-set", "Rent", "1500") == 0
assert "$1,500.00" in capsys.readouterr().out
assert run(db, "budget-set", "rent", "2000") == 0 # replace
assert "$2,000.00" in capsys.readouterr().out
assert run(db, "budget-del", "Rent") == 0
assert "removed budget" in capsys.readouterr().out
def test_budget_set_income_category(db, capsys):
assert run(db, "budget-set", "Salary", "100") == 1
assert "expense" in capsys.readouterr().err
def test_budget_set_invalid_amount(db):
with pytest.raises(SystemExit) as exc:
run(db, "budget-set", "Rent", "not-money")
assert exc.value.code == 2
def test_budget_del_not_set(db, capsys):
assert run(db, "budget-del", "Rent") == 1
assert "no budget" in capsys.readouterr().err
def test_budget_show_with_overage(db, capsys):
run(db, "budget-set", "Rent", "1500")
run(db, "add", "1600.00", "--category", "Rent", "--date", "2026-10-05")
assert run(db, "budget-show", "--month", "2026-10") == 0
out = capsys.readouterr().out
assert "Rent" in out and "OVER" in out and "budgets for 2026-10" in out
def test_budget_show_flags_unbudgeted_spending(db, capsys):
run(db, "add", "40.00", "--category", "Groceries", "--date", "2026-10-02")
assert run(db, "budget-show", "--month", "2026-10") == 0
out = capsys.readouterr().out
assert "Groceries" in out and "OVER" in out
def test_budget_show_no_budgets_no_spending(db, capsys):
assert run(db, "budget-show", "--month", "2026-10") == 0
assert "no budgets set" in capsys.readouterr().out
# -- report ---------------------------------------------------------------------------
def test_report(db, capsys):
run(db, "add", "3000", "--category", "Salary", "--date", "2026-10-01")
run(db, "add", "1200", "--category", "Rent", "--date", "2026-10-02")
run(db, "add", "250", "--category", "Groceries", "--date", "2026-10-03")
assert run(db, "report", "--month", "2026-10") == 0
out = capsys.readouterr().out
assert "report for 2026-10" in out
assert "income" in out and "$3,000.00" in out
assert "expenses" in out and "$1,450.00" in out
assert "net" in out and "$1,550.00" in out
assert "Salary" in out and "Groceries" in out
def test_report_empty(db, capsys):
assert run(db, "report", "--month", "2001-01") == 0
assert "no transactions" in capsys.readouterr().out
def test_report_default_month(db, capsys):
assert run(db, "report") == 0
assert "report for" in capsys.readouterr().out
def test_invalid_month(db):
with pytest.raises(SystemExit) as exc:
run(db, "report", "--month", "2026-99")
assert exc.value.code == 2