feat(logging): seed month + posting events (#27)
This commit is contained in:
parent
41ee888d3b
commit
7340a66988
2 changed files with 112 additions and 0 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import date, datetime, timezone
|
from datetime import date, datetime, timezone
|
||||||
|
|
@ -28,6 +29,8 @@ from quartermaster.models import (
|
||||||
Section,
|
Section,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger("quartermaster.month_service")
|
||||||
|
|
||||||
YEAR_MONTH_RE = re.compile(r"^\d{4}-(0[1-9]|1[0-2])$")
|
YEAR_MONTH_RE = re.compile(r"^\d{4}-(0[1-9]|1[0-2])$")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -148,6 +151,10 @@ def create_month(db: Session, year_month: str) -> Month:
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(month)
|
db.refresh(month)
|
||||||
|
logger.info(
|
||||||
|
"created month snapshot",
|
||||||
|
extra={"event": "month_created", "year_month": month.year_month},
|
||||||
|
)
|
||||||
return month
|
return month
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -314,6 +321,15 @@ def add_posting(
|
||||||
db.add(posting)
|
db.add(posting)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(posting)
|
db.refresh(posting)
|
||||||
|
logger.info(
|
||||||
|
"added posting",
|
||||||
|
extra={
|
||||||
|
"event": "posting_added",
|
||||||
|
"posting_id": posting.id,
|
||||||
|
"month_entry_id": posting.month_entry_id,
|
||||||
|
"amount": str(posting.amount),
|
||||||
|
},
|
||||||
|
)
|
||||||
return posting
|
return posting
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -368,6 +384,10 @@ def delete_posting(
|
||||||
entry = posting.entry
|
entry = posting.entry
|
||||||
db.delete(posting)
|
db.delete(posting)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
logger.info(
|
||||||
|
"deleted posting",
|
||||||
|
extra={"event": "posting_deleted", "posting_id": posting_id},
|
||||||
|
)
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -428,6 +448,10 @@ def close_month(db: Session, month: Month) -> Month:
|
||||||
month.closed_at = datetime.now(timezone.utc)
|
month.closed_at = datetime.now(timezone.utc)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(month)
|
db.refresh(month)
|
||||||
|
logger.info(
|
||||||
|
"closed month",
|
||||||
|
extra={"event": "month_closed", "year_month": month.year_month},
|
||||||
|
)
|
||||||
return month
|
return month
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,14 @@ from __future__ import annotations
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from quartermaster import month_service, service
|
||||||
from quartermaster.logging_config import LOG_CONFIG
|
from quartermaster.logging_config import LOG_CONFIG
|
||||||
|
from quartermaster.models import Section
|
||||||
|
|
||||||
|
|
||||||
def _build_formatter():
|
def _build_formatter():
|
||||||
|
|
@ -89,3 +95,85 @@ def test_log_config_loads_via_dictconfig():
|
||||||
from quartermaster.logging_config import LOG_CONFIG
|
from quartermaster.logging_config import LOG_CONFIG
|
||||||
|
|
||||||
logging.config.dictConfig(LOG_CONFIG)
|
logging.config.dictConfig(LOG_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _reset_quartermaster_logger():
|
||||||
|
"""Ensure the quartermaster logger propagates to root so caplog can capture records.
|
||||||
|
|
||||||
|
logging.config.dictConfig (called in test_log_config_loads_via_dictconfig) sets
|
||||||
|
propagate=False on the quartermaster logger. caplog injects its handler into the
|
||||||
|
root logger, so records only reach it when propagation is enabled. This fixture
|
||||||
|
temporarily re-enables propagation for every test in this module and restores the
|
||||||
|
original value on teardown.
|
||||||
|
"""
|
||||||
|
qm_logger = logging.getLogger("quartermaster")
|
||||||
|
original_propagate = qm_logger.propagate
|
||||||
|
qm_logger.propagate = True
|
||||||
|
yield
|
||||||
|
qm_logger.propagate = original_propagate
|
||||||
|
|
||||||
|
|
||||||
|
def _make_debt_minimum(db, name="Loan", amount=Decimal("100.00")):
|
||||||
|
from quartermaster.models import Entry
|
||||||
|
entry = Entry(section=Section.debt_minimum, name=name, amount=amount)
|
||||||
|
db.add(entry)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(entry)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_month_logs_month_created_event(db, caplog):
|
||||||
|
caplog.set_level(logging.INFO, logger="quartermaster")
|
||||||
|
month_service.create_month(db, "2026-05")
|
||||||
|
|
||||||
|
events = [r for r in caplog.records if getattr(r, "event", None) == "month_created"]
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].levelname == "INFO"
|
||||||
|
|
||||||
|
|
||||||
|
def test_close_month_logs_month_closed_event(db, caplog):
|
||||||
|
month = month_service.create_month(db, "2026-06")
|
||||||
|
# move through active → closed with a zero-applied budget (no entries yet)
|
||||||
|
month_service.activate_month(db, month)
|
||||||
|
caplog.clear()
|
||||||
|
caplog.set_level(logging.INFO, logger="quartermaster")
|
||||||
|
|
||||||
|
month_service.close_month(db, month)
|
||||||
|
|
||||||
|
events = [r for r in caplog.records if getattr(r, "event", None) == "month_closed"]
|
||||||
|
assert len(events) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_posting_logs_posting_added_event(db, caplog):
|
||||||
|
_make_debt_minimum(db)
|
||||||
|
month = month_service.create_month(db, "2026-07")
|
||||||
|
entry = month.entries[0]
|
||||||
|
caplog.set_level(logging.INFO, logger="quartermaster")
|
||||||
|
|
||||||
|
month_service.add_posting(
|
||||||
|
db, month, entry.id,
|
||||||
|
occurred_on=date(2026, 7, 15),
|
||||||
|
amount=Decimal("25.00"),
|
||||||
|
)
|
||||||
|
|
||||||
|
events = [r for r in caplog.records if getattr(r, "event", None) == "posting_added"]
|
||||||
|
assert len(events) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_posting_logs_posting_deleted_event(db, caplog):
|
||||||
|
_make_debt_minimum(db)
|
||||||
|
month = month_service.create_month(db, "2026-08")
|
||||||
|
entry = month.entries[0]
|
||||||
|
posting = month_service.add_posting(
|
||||||
|
db, month, entry.id,
|
||||||
|
occurred_on=date(2026, 8, 15),
|
||||||
|
amount=Decimal("10.00"),
|
||||||
|
)
|
||||||
|
caplog.clear()
|
||||||
|
caplog.set_level(logging.INFO, logger="quartermaster")
|
||||||
|
|
||||||
|
month_service.delete_posting(db, month, posting.id)
|
||||||
|
|
||||||
|
events = [r for r in caplog.records if getattr(r, "event", None) == "posting_deleted"]
|
||||||
|
assert len(events) == 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue