Initial commit: habit tracker PWA with Google OAuth, push notifications
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>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
from pywebpush import WebPushException
|
||||
|
||||
from app.models.push_subscription import PushSubscription
|
||||
from app.schemas.push import PushKeys, PushSubscribeRequest
|
||||
from app.services import push_service
|
||||
|
||||
|
||||
class _FakeResponse:
|
||||
def __init__(self, status_code):
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
def _subscribe_request(endpoint="https://push.example.com/1"):
|
||||
return PushSubscribeRequest(endpoint=endpoint, keys=PushKeys(p256dh="p256dh-key", auth="auth-key"))
|
||||
|
||||
|
||||
# ---- save_subscription ----
|
||||
|
||||
|
||||
def test_save_subscription_creates_new_row(db_session, test_user):
|
||||
sub = push_service.save_subscription(db_session, test_user.id, _subscribe_request())
|
||||
assert sub.user_id == test_user.id
|
||||
assert sub.endpoint == "https://push.example.com/1"
|
||||
|
||||
|
||||
def test_save_subscription_reassigns_existing_endpoint_to_new_user(db_session, test_user, other_user):
|
||||
request = _subscribe_request()
|
||||
push_service.save_subscription(db_session, other_user.id, request)
|
||||
|
||||
# 같은 기기(endpoint)에서 다른 유저(test_user)로 재구독하면 소유자가 갱신되어야 한다.
|
||||
updated = push_service.save_subscription(db_session, test_user.id, request)
|
||||
|
||||
assert updated.user_id == test_user.id
|
||||
assert db_session.query(PushSubscription).filter_by(endpoint=request.endpoint).count() == 1
|
||||
|
||||
|
||||
def test_delete_subscription_removes_row(db_session, test_user):
|
||||
sub = push_service.save_subscription(db_session, test_user.id, _subscribe_request())
|
||||
push_service.delete_subscription(db_session, sub.endpoint)
|
||||
assert db_session.query(PushSubscription).filter_by(endpoint=sub.endpoint).count() == 0
|
||||
|
||||
|
||||
# ---- send_to_user ----
|
||||
|
||||
|
||||
def test_send_to_user_counts_successful_sends(db_session, test_user, monkeypatch):
|
||||
push_service.save_subscription(db_session, test_user.id, _subscribe_request("https://push.example.com/a"))
|
||||
push_service.save_subscription(db_session, test_user.id, _subscribe_request("https://push.example.com/b"))
|
||||
|
||||
monkeypatch.setattr(push_service, "webpush", lambda **kwargs: None)
|
||||
|
||||
sent = push_service.send_to_user(db_session, test_user.id, title="제목", body="본문")
|
||||
assert sent == 2
|
||||
|
||||
|
||||
def test_send_to_user_deletes_expired_subscription_on_410(db_session, test_user, monkeypatch):
|
||||
sub = push_service.save_subscription(db_session, test_user.id, _subscribe_request())
|
||||
|
||||
def _raise_gone(**kwargs):
|
||||
raise WebPushException("gone", response=_FakeResponse(410))
|
||||
|
||||
monkeypatch.setattr(push_service, "webpush", _raise_gone)
|
||||
|
||||
sent = push_service.send_to_user(db_session, test_user.id, title="제목", body="본문")
|
||||
assert sent == 0
|
||||
assert db_session.query(PushSubscription).filter_by(id=sub.id).count() == 0
|
||||
|
||||
|
||||
def test_send_to_user_keeps_subscription_on_other_errors(db_session, test_user, monkeypatch):
|
||||
sub = push_service.save_subscription(db_session, test_user.id, _subscribe_request())
|
||||
|
||||
def _raise_server_error(**kwargs):
|
||||
raise WebPushException("server error", response=_FakeResponse(500))
|
||||
|
||||
monkeypatch.setattr(push_service, "webpush", _raise_server_error)
|
||||
|
||||
sent = push_service.send_to_user(db_session, test_user.id, title="제목", body="본문")
|
||||
assert sent == 0
|
||||
assert db_session.query(PushSubscription).filter_by(id=sub.id).count() == 1
|
||||
Reference in New Issue
Block a user