FastAPI + SQLAlchemy/Alembic + MariaDB backend with Jinja2/htmx/Alpine server-rendered frontend. Multi-user via Google OAuth, daily habit tracking, monthly/weekly history views, Web Push reminders via APScheduler, and PWA support (manifest, service worker, offline caching). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
111 lines
4.2 KiB
Python
111 lines
4.2 KiB
Python
from datetime import date, datetime, timedelta
|
|
|
|
from app.models.habit import ALL_WEEKDAYS_MASK, HabitType
|
|
from app.models.notification_log import SummaryNotificationLog
|
|
from app.schemas.habit import HabitCreate
|
|
from app.schemas.push import PushKeys, PushSubscribeRequest
|
|
from app.services import habit_service, push_service, scheduler_service
|
|
|
|
# scheduler_service._tick/_weekly_summary_tick/_monthly_summary_tick은 자체적으로
|
|
# app.database.SessionLocal()을 열어 실제 운영 DB에 붙으므로(conftest의 client 픽스처가 lifespan을
|
|
# 건너뛰는 이유와 동일) 여기서는 db_session을 직접 주입할 수 있는 _send_period_summaries/
|
|
# _claim_summary_slot만 단위 테스트한다.
|
|
|
|
|
|
def _make_habit(db_session, user_id, created_at=None, **overrides):
|
|
data = HabitCreate(
|
|
name=overrides.pop("name", "테스트 습관"),
|
|
habit_type=overrides.pop("habit_type", HabitType.BUILD),
|
|
weekdays_mask=overrides.pop("weekdays_mask", ALL_WEEKDAYS_MASK),
|
|
condition_text=overrides.pop("condition_text", None),
|
|
reminder_time=overrides.pop("reminder_time", None),
|
|
)
|
|
habit = habit_service.create_habit(db_session, user_id, data)
|
|
if created_at is not None:
|
|
habit.created_at = created_at
|
|
db_session.commit()
|
|
db_session.refresh(habit)
|
|
return habit
|
|
|
|
|
|
def _subscribe(db_session, user_id, endpoint):
|
|
push_service.save_subscription(
|
|
db_session, user_id, PushSubscribeRequest(endpoint=endpoint, keys=PushKeys(p256dh="p", auth="a"))
|
|
)
|
|
|
|
|
|
# ---- _claim_summary_slot ----
|
|
|
|
|
|
def test_claim_summary_slot_prevents_duplicate(db_session, test_user):
|
|
today = date.today()
|
|
assert scheduler_service._claim_summary_slot(db_session, test_user.id, "weekly", today) is True
|
|
assert scheduler_service._claim_summary_slot(db_session, test_user.id, "weekly", today) is False
|
|
assert db_session.query(SummaryNotificationLog).filter_by(user_id=test_user.id).count() == 1
|
|
|
|
|
|
# ---- _send_period_summaries ----
|
|
|
|
|
|
def test_send_period_summaries_sends_push_and_claims_slot(db_session, test_user, monkeypatch):
|
|
sent = []
|
|
monkeypatch.setattr(push_service, "webpush", lambda **kwargs: sent.append(kwargs))
|
|
_subscribe(db_session, test_user.id, "https://push.example.com/x")
|
|
|
|
today = date.today()
|
|
_make_habit(db_session, test_user.id, created_at=datetime.combine(today, datetime.min.time()))
|
|
|
|
scheduler_service._send_period_summaries(
|
|
db_session,
|
|
period_type="weekly",
|
|
period_start=today,
|
|
range_start=today,
|
|
range_end=today,
|
|
title="이번 주 습관 리포트",
|
|
url="/history",
|
|
)
|
|
|
|
assert len(sent) == 1
|
|
assert (
|
|
db_session.query(SummaryNotificationLog).filter_by(user_id=test_user.id, period_type="weekly").count() == 1
|
|
)
|
|
|
|
|
|
def test_send_period_summaries_skips_when_nothing_scheduled_in_range(db_session, test_user, monkeypatch):
|
|
sent = []
|
|
monkeypatch.setattr(push_service, "webpush", lambda **kwargs: sent.append(kwargs))
|
|
_subscribe(db_session, test_user.id, "https://push.example.com/y")
|
|
|
|
today = date.today()
|
|
# 습관이 range_end 이후에 생성되어, 요청한 기간에는 예정된 게 하나도 없다.
|
|
_make_habit(db_session, test_user.id, created_at=datetime.combine(today + timedelta(days=1), datetime.min.time()))
|
|
|
|
scheduler_service._send_period_summaries(
|
|
db_session, period_type="weekly", period_start=today, range_start=today, range_end=today, title="t", url="/h"
|
|
)
|
|
|
|
assert sent == []
|
|
assert db_session.query(SummaryNotificationLog).count() == 0
|
|
|
|
|
|
def test_send_period_summaries_does_not_resend_when_already_claimed(db_session, test_user, monkeypatch):
|
|
sent = []
|
|
monkeypatch.setattr(push_service, "webpush", lambda **kwargs: sent.append(kwargs))
|
|
_subscribe(db_session, test_user.id, "https://push.example.com/z")
|
|
|
|
today = date.today()
|
|
_make_habit(db_session, test_user.id, created_at=datetime.combine(today, datetime.min.time()))
|
|
|
|
for _ in range(2):
|
|
scheduler_service._send_period_summaries(
|
|
db_session,
|
|
period_type="weekly",
|
|
period_start=today,
|
|
range_start=today,
|
|
range_end=today,
|
|
title="t",
|
|
url="/h",
|
|
)
|
|
|
|
assert len(sent) == 1
|