T4: Repository pattern — ModelRepository, JsonFileRepository, ExcelRepository

- application/repositories/model_repository.py: ModelRepository Protocol
  with load(path) and save(model, path)
- infrastructure/repositories/json_file_repository.py: JsonFileRepository
  (вынесен из FinancialModel.save/load)
- infrastructure/repositories/excel_repository.py: ExcelRepository
  (бывший ExcelSync, переименован)
- FinancialModel.save/load удалены — теперь только данные
- infrastructure/sync/ удалён (ExcelSync перенесён в repositories/)
- infrastructure/cli/main.py и config.py используют JsonFileRepository +
  ExcelRepository через локальные singletons
- test_excel_sync.py переименован в test для ExcelRepository
- Новый test_repositories.py: 5 тестов для JsonFileRepository + протокол
- test_model.py: использован JsonFileRepository

Tests: 68/68 pass (было 63 + 5 новых).
This commit is contained in:
2026-10-08 16:50:06 +03:00
parent 363d440d53
commit 3f7268d14c
12 changed files with 190 additions and 92 deletions
+16 -16
View File
@@ -1,22 +1,22 @@
from pathlib import Path
from domain import FinancialModel
from infrastructure.sync.excel_sync import ExcelSync, SyncError
from infrastructure.repositories import ExcelRepository, ExcelRepositoryError
class TestExcelSync:
class TestExcelRepository:
def test_export_creates_file(self, sample_model, tmp_path: Path):
sync = ExcelSync()
repo = ExcelRepository()
p = tmp_path / "test.xlsx"
sync.export_model(sample_model, p)
repo.save(sample_model, p)
assert p.exists()
def test_export_import_roundtrip(self, sample_model, tmp_path: Path):
sync = ExcelSync()
repo = ExcelRepository()
p = tmp_path / "roundtrip.xlsx"
sync.export_model(sample_model, p)
repo.save(sample_model, p)
loaded = sync.import_model(p)
loaded = repo.load(p)
assert len(loaded.accounts) == len(sample_model.accounts)
assert len(loaded.transactions) == len(sample_model.transactions)
assert len(loaded.recurring) == len(sample_model.recurring)
@@ -25,25 +25,25 @@ class TestExcelSync:
assert len(loaded.exchange_rates) == len(sample_model.exchange_rates)
def test_roundtrip_preserves_base_currency(self, tmp_path: Path):
sync = ExcelSync()
repo = ExcelRepository()
model = FinancialModel(base_currency="EUR")
p = tmp_path / "eur_model.xlsx"
sync.export_model(model, p)
repo.save(model, p)
loaded = sync.import_model(p)
loaded = repo.load(p)
assert loaded.base_currency == "EUR"
def test_import_missing_file(self):
sync = ExcelSync()
repo = ExcelRepository()
try:
sync.import_model("nonexistent.xlsx")
assert False, "Expected SyncError"
except SyncError:
repo.load("nonexistent.xlsx")
assert False, "Expected ExcelRepositoryError"
except ExcelRepositoryError:
pass
def test_export_empty_model(self, tmp_path: Path):
sync = ExcelSync()
repo = ExcelRepository()
model = FinancialModel()
p = tmp_path / "empty.xlsx"
sync.export_model(model, p)
repo.save(model, p)
assert p.exists()