test_sample_files.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. """Coverage for bundled fictional bill files and the explicit CSV CLI."""
  2. from __future__ import annotations
  3. from pathlib import Path
  4. import pytest
  5. from main import _parse_goal, parser
  6. from src.agents.coordinator import MoneyMirrorCoordinator
  7. from src.tools import CSVImportTool
  8. from .fakes import FakeRuntime
  9. ROOT = Path(__file__).resolve().parents[1]
  10. SAMPLE_FILES = tuple(ROOT / "data" / f"sample_{index:02d}.csv" for index in range(1, 6))
  11. def test_all_bundled_sample_files_exist_and_import() -> None:
  12. importer = CSVImportTool()
  13. assert len(SAMPLE_FILES) == 5
  14. for index, path in enumerate(SAMPLE_FILES, start=1):
  15. assert path.is_file(), f"sample-{index:02d} is missing its CSV: {path}"
  16. transactions = importer.load(path)
  17. assert transactions, f"sample-{index:02d} should contain transactions"
  18. assert not importer.last_errors, f"sample-{index:02d} has invalid rows: {importer.last_errors}"
  19. def test_samples_produce_distinct_data_grounded_quest_signals(tmp_path) -> None:
  20. quest_ids: dict[str, set[str]] = {}
  21. subscription_merchants: dict[str, set[str]] = {}
  22. for index, path in enumerate(SAMPLE_FILES, start=1):
  23. sample_id = f"sample-{index:02d}"
  24. coordinator = MoneyMirrorCoordinator(tmp_path / f"{sample_id}.db", runtime=FakeRuntime())
  25. try:
  26. report = coordinator.analyze_csv(path)
  27. assert report.summary["income"] > 0
  28. assert report.summary["expense"] > 0
  29. assert report.quests
  30. quest_ids[sample_id] = {quest.quest_id for quest in report.quests}
  31. subscription_merchants[sample_id] = {item["merchant"] for item in report.subscriptions}
  32. finally:
  33. coordinator.close()
  34. assert "weekend_wallet_shield" in quest_ids["sample-03"]
  35. assert "payday_cooldown" in quest_ids["sample-04"]
  36. assert "learning_loot_log" in quest_ids["sample-02"]
  37. assert "subscription_hunter" in quest_ids["sample-05"]
  38. assert "房东-六月房租" not in subscription_merchants["sample-04"]
  39. assert "腾讯视频会员" in subscription_merchants["sample-05"]
  40. assert len({tuple(sorted(ids)) for ids in quest_ids.values()}) >= 4
  41. def test_csv_path_is_required_and_demo_flag_is_removed() -> None:
  42. command = parser()
  43. args = command.parse_args(["--csv", "bill.csv"])
  44. assert args.csv == Path("bill.csv")
  45. assert args.interactive is False
  46. assert not hasattr(args, "demo")
  47. interactive_args = command.parse_args(["--interactive", "--csv", "bill.csv"])
  48. assert interactive_args.interactive is True
  49. with pytest.raises(SystemExit):
  50. command.parse_args([])
  51. with pytest.raises(SystemExit):
  52. command.parse_args(["--demo", "--csv", "bill.csv"])
  53. def test_cli_goal_parsing_is_explicit_and_validated() -> None:
  54. travel = _parse_goal("三个月旅行基金|travel|10000|2800|2026-10-31")
  55. assert travel.goal_type == "travel"
  56. assert travel.target_amount == 10000
  57. assert travel.current_amount == 2800
  58. category = _parse_goal("本月娱乐限额|category_limit|800|0|2026-08-31|娱乐|800")
  59. assert category.category == "娱乐"
  60. assert category.monthly_limit == 800
  61. with pytest.raises(ValueError, match="格式"):
  62. _parse_goal("格式不完整|travel")
  63. with pytest.raises(ValueError, match="category_limit"):
  64. _parse_goal("娱乐限额|category_limit|800|0|2026-08-31")