"""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