"""Tests for the book-close automation crons (PR6): auto roll-forward + reminder.""" from __future__ import annotations import uuid from datetime import timedelta import pytest from app.domains.finance import close_task_service as svc from app.scheduled_jobs.finance_handlers import ( _china_period, _china_today, _shift_period, run_finance_close_auto_roll_forward, run_finance_close_deadline_reminder, ) @pytest.mark.asyncio async def test_auto_roll_forward_clones_prev_into_current(db_session): current = _china_period() prev = _shift_period(current, 1) await svc.instantiate_from_templates(db_session, prev) # current empty → handler rolls prev → current. res = await run_finance_close_auto_roll_forward(db_session, {}) assert res["rolled"] is True assert res["period"] == current assert len(await svc.list_tasks(db_session, current)) == 129 # idempotent — second run is a no-op (current now has tasks). res2 = await run_finance_close_auto_roll_forward(db_session, {}) assert res2["rolled"] is False @pytest.mark.asyncio async def test_auto_roll_forward_noop_without_source(db_session): res = await run_finance_close_auto_roll_forward(db_session, {}) assert res["rolled"] is False assert "no source" in res["reason"] @pytest.mark.asyncio async def test_deadline_reminder_notifies_owner(db_session): from sqlalchemy import select from app.identity.person_models import IdentityPerson from app.notifications.models import NotificationOutbox current = _china_period() pid = uuid.uuid4() db_session.add(IdentityPerson( id=pid, hris_person_id="sf-owner", work_email="owner@pingcap.com", )) await db_session.flush() await svc.instantiate_from_templates(db_session, current, actor_id=str(pid)) # Assign one task to the person with an overdue deadline. tasks = await svc.list_tasks(db_session, current) t0 = tasks[0] t0.owner_person_id = str(pid) t0.deadline = _china_today() - timedelta(days=3) t0.status = "not_started" await db_session.flush() res = await run_finance_close_deadline_reminder(db_session, {"within_days": 2}) assert res["reminded"] >= 1 # An inbox row was written for the owner. rows = (await db_session.execute( select(NotificationOutbox).where( NotificationOutbox.recipient_person_id == pid, NotificationOutbox.template_key == "finance.close_reminder", ) )).scalars().all() assert len(rows) == 1 @pytest.mark.asyncio async def test_deadline_reminder_noop_when_nothing_due(db_session): current = _china_period() await svc.instantiate_from_templates(db_session, current) # No task is both owned-by-a-resolved-person AND due within window with a # real deadline + overdue, so nothing to remind (owners unresolved in test). res = await run_finance_close_deadline_reminder(db_session, {"within_days": 2}) assert res["reminded"] == 0