forked from retoor/devplacepy
Unit tests for the provider registry, both providers' registration parsing, the APNs payload translation, provider token signing and caching, header and status mapping against a mock transport, provider grouping and the delivery timeout clamp, plus service tests for the configuration surface, the retention sweep and the per-provider metrics. Api tests cover the provider listing on GET /push.json, registration with and without an explicit provider, idempotency and the rejection of an unknown or unconfigured provider. Provider settings in unit tests are supplied by monkeypatching the provider's setting reader rather than writing site_settings, because the unit tier shares its database with the running api-tier server. devplacepy/push/CLAUDE.md documents the protocol, how to add a provider, the invariants and the APNs specifics; the root, routers and services files point at it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from devplacepy.database import get_table
|
|
from devplacepy.push import store
|
|
from devplacepy.services.push import PushService
|
|
from devplacepy.utils import generate_uid
|
|
|
|
|
|
def _registration(user_uid, deleted_at=None, provider="webpush"):
|
|
record = {
|
|
"uid": generate_uid(),
|
|
"user_uid": user_uid,
|
|
"provider": provider,
|
|
"endpoint": f"https://push.example.com/{generate_uid()}",
|
|
"key_auth": "a",
|
|
"key_p256dh": "p",
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"deleted_at": deleted_at,
|
|
}
|
|
get_table("push_registration").insert(record)
|
|
return record
|
|
|
|
|
|
def test_config_fields_cover_every_provider(local_db):
|
|
keys = [field.key for field in PushService().all_fields()]
|
|
for expected in (
|
|
"service_push_enabled",
|
|
"push_dead_retention_days",
|
|
"push_delivery_timeout_seconds",
|
|
"push_webpush_enabled",
|
|
"push_webpush_subject",
|
|
"push_apns_enabled",
|
|
"push_apns_team_id",
|
|
"push_apns_key_id",
|
|
"push_apns_auth_key",
|
|
"push_apns_topic",
|
|
"push_apns_environment",
|
|
):
|
|
assert expected in keys
|
|
|
|
|
|
def test_apns_auth_key_field_is_a_masked_secret(local_db):
|
|
fields = {field.key: field for field in PushService().all_fields()}
|
|
auth_key = fields["push_apns_auth_key"]
|
|
assert auth_key.secret is True
|
|
assert auth_key.type == "text"
|
|
assert auth_key.display_value() == ""
|
|
|
|
|
|
def test_run_once_prunes_only_stale_dead_rows(local_db, monkeypatch):
|
|
from devplacepy.services import push as push_service
|
|
|
|
user_uid = f"prune_{generate_uid()}"
|
|
live = _registration(user_uid)
|
|
fresh_dead = _registration(
|
|
user_uid,
|
|
deleted_at=(datetime.now(timezone.utc) - timedelta(days=1)).isoformat(),
|
|
)
|
|
stale_dead = _registration(
|
|
user_uid,
|
|
deleted_at=(datetime.now(timezone.utc) - timedelta(days=90)).isoformat(),
|
|
)
|
|
|
|
service = PushService()
|
|
monkeypatch.setattr(
|
|
push_service.service, "get_int_setting", lambda key, default: 30
|
|
)
|
|
from tests.conftest import run_async
|
|
|
|
run_async(service.run_once())
|
|
|
|
registrations = get_table("push_registration")
|
|
assert registrations.find_one(uid=live["uid"]) is not None
|
|
assert registrations.find_one(uid=fresh_dead["uid"]) is not None
|
|
assert registrations.find_one(uid=stale_dead["uid"]) is None
|
|
|
|
|
|
def test_run_once_with_retention_disabled_prunes_nothing(local_db, monkeypatch):
|
|
from devplacepy.services import push as push_service
|
|
from tests.conftest import run_async
|
|
|
|
user_uid = f"keep_{generate_uid()}"
|
|
stale_dead = _registration(
|
|
user_uid,
|
|
deleted_at=(datetime.now(timezone.utc) - timedelta(days=900)).isoformat(),
|
|
)
|
|
|
|
service = PushService()
|
|
monkeypatch.setattr(push_service.service, "get_int_setting", lambda key, default: 0)
|
|
run_async(service.run_once())
|
|
|
|
assert get_table("push_registration").find_one(uid=stale_dead["uid"]) is not None
|
|
|
|
|
|
def test_metrics_count_live_rows_per_provider(local_db):
|
|
user_uid = f"metrics_{generate_uid()}"
|
|
_registration(user_uid)
|
|
_registration(user_uid, provider="apns")
|
|
|
|
metrics = PushService().collect_metrics()
|
|
assert metrics["webpush_active"] >= 1
|
|
assert metrics["apns_active"] >= 1
|
|
assert metrics["webpush_ready"] == 1
|
|
assert "dead" in metrics
|
|
|
|
|
|
def test_store_counts_treats_a_missing_provider_as_webpush(local_db):
|
|
user_uid = f"legacy_{generate_uid()}"
|
|
record = _registration(user_uid)
|
|
get_table("push_registration").update(
|
|
{"id": get_table("push_registration").find_one(uid=record["uid"])["id"], "provider": None},
|
|
["id"],
|
|
)
|
|
|
|
assert store.counts().get("webpush", 0) >= 1
|