2026-03-29 20:34:12 +08:00
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
|
|
|
|
|
|
import src.auth.supabase_entitlement as entitlement_module
|
|
|
|
|
from src.auth.supabase_entitlement import SupabaseEntitlementService
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _Response:
|
|
|
|
|
def __init__(self, status_code=200, payload=None):
|
|
|
|
|
self.status_code = status_code
|
|
|
|
|
self._payload = payload
|
|
|
|
|
self.content = b"1"
|
|
|
|
|
|
|
|
|
|
def json(self):
|
|
|
|
|
return self._payload
|
|
|
|
|
|
|
|
|
|
|
2026-04-07 09:54:21 +08:00
|
|
|
def test_latest_active_subscription_ignores_future_start(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
|
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
|
current_trial = {
|
|
|
|
|
"id": 1,
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"status": "active",
|
|
|
|
|
"plan_code": "signup_trial_3d",
|
|
|
|
|
"starts_at": (now - timedelta(days=1)).isoformat(),
|
|
|
|
|
"expires_at": (now + timedelta(days=2)).isoformat(),
|
|
|
|
|
}
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
2026-05-29 17:22:33 +08:00
|
|
|
assert params["select"] == "plan_code,source,starts_at,expires_at"
|
|
|
|
|
assert str(params["starts_at"]).startswith("lte.")
|
|
|
|
|
assert params["limit"] == "1"
|
|
|
|
|
return _Response(200, [current_trial])
|
2026-04-07 09:54:21 +08:00
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
result = service._query_latest_active_subscription("user-1")
|
|
|
|
|
|
|
|
|
|
assert result is not None
|
|
|
|
|
assert result["plan_code"] == "signup_trial_3d"
|
2026-04-19 20:19:38 +08:00
|
|
|
|
|
|
|
|
|
2026-05-29 17:22:33 +08:00
|
|
|
def test_get_identity_caches_invalid_token_result(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = {"count": 0}
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls["count"] += 1
|
|
|
|
|
return _Response(401, {"message": "invalid token"})
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.get_identity("bad-token") is None
|
|
|
|
|
assert service.get_identity("bad-token") is None
|
|
|
|
|
assert calls["count"] == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_identity_does_not_cache_transient_auth_errors(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = {"count": 0}
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls["count"] += 1
|
|
|
|
|
return _Response(503, {"message": "temporarily unavailable"})
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.get_identity("temporarily-bad-token") is None
|
|
|
|
|
assert service.get_identity("temporarily-bad-token") is None
|
|
|
|
|
assert calls["count"] == 2
|
|
|
|
|
|
|
|
|
|
|
2026-04-19 20:19:38 +08:00
|
|
|
def test_subscription_window_keeps_queued_renewal_after_current_cache(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
|
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
|
current = {
|
|
|
|
|
"id": 1,
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"status": "active",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"starts_at": (now - timedelta(days=29)).isoformat(),
|
|
|
|
|
"expires_at": (now + timedelta(days=1)).isoformat(),
|
|
|
|
|
}
|
|
|
|
|
queued = {
|
|
|
|
|
"id": 2,
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"status": "active",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"starts_at": (now + timedelta(days=1)).isoformat(),
|
|
|
|
|
"expires_at": (now + timedelta(days=31)).isoformat(),
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 17:22:33 +08:00
|
|
|
calls = []
|
|
|
|
|
|
2026-04-19 20:19:38 +08:00
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
2026-05-29 17:22:33 +08:00
|
|
|
calls.append(params)
|
|
|
|
|
assert params["select"] == "plan_code,source,starts_at,expires_at"
|
|
|
|
|
if params["limit"] == "1":
|
|
|
|
|
assert str(params["starts_at"]).startswith("lte.")
|
|
|
|
|
return _Response(200, [current])
|
|
|
|
|
assert params["limit"] == "100"
|
|
|
|
|
assert "starts_at" not in params
|
2026-04-19 20:19:38 +08:00
|
|
|
return _Response(200, [queued, current])
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service._query_latest_active_subscription("user-1") == current
|
|
|
|
|
|
|
|
|
|
window = service.get_subscription_window("user-1", respect_requirement=False)
|
|
|
|
|
|
|
|
|
|
assert window["current"] == current
|
|
|
|
|
assert window["total_expires_at"] == queued["expires_at"]
|
|
|
|
|
assert window["queued_days"] == 30
|
|
|
|
|
assert window["queued_count"] == 1
|
2026-05-29 17:22:33 +08:00
|
|
|
assert len(calls) == 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_subscription_window_query_selects_only_window_fields(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
assert params["select"] == "plan_code,source,starts_at,expires_at"
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"source": "payment_contract",
|
|
|
|
|
"starts_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
"expires_at": "2099-04-01T00:00:00+00:00",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
window = service.get_subscription_window(
|
|
|
|
|
"user-1",
|
|
|
|
|
respect_requirement=False,
|
|
|
|
|
bypass_cache=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert window["current"]["plan_code"] == "pro_monthly"
|
|
|
|
|
|
|
|
|
|
|
2026-05-30 18:34:38 +08:00
|
|
|
def test_subscription_window_can_report_unknown_on_transient_query_failure(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
return _Response(503, {"message": "temporarily unavailable"})
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
window = service.get_subscription_window(
|
|
|
|
|
"user-1",
|
|
|
|
|
respect_requirement=False,
|
|
|
|
|
bypass_cache=True,
|
|
|
|
|
unknown_on_error=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert window["unknown"] is True
|
|
|
|
|
assert window["rows"] is None
|
|
|
|
|
|
|
|
|
|
|
2026-05-29 17:22:33 +08:00
|
|
|
def test_list_subscription_windows_selects_only_batch_window_fields(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
assert params["select"] == "user_id,plan_code,source,starts_at,expires_at"
|
|
|
|
|
assert params["user_id"] == "in.(user-1,user-2)"
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"source": "payment_contract",
|
|
|
|
|
"starts_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
"expires_at": "2099-04-01T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"user_id": "user-2",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"source": "payment_contract",
|
|
|
|
|
"starts_at": "2026-03-02T00:00:00+00:00",
|
|
|
|
|
"expires_at": "2099-04-02T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
windows = service.list_subscription_windows(
|
|
|
|
|
["user-1", "user-2"],
|
|
|
|
|
bypass_cache=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert set(windows) == {"user-1", "user-2"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_list_active_subscription_windows_uses_single_window_query(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = []
|
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
|
current = {
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"source": "payment_contract",
|
|
|
|
|
"starts_at": (now - timedelta(days=1)).isoformat(),
|
|
|
|
|
"expires_at": (now + timedelta(days=10)).isoformat(),
|
|
|
|
|
}
|
|
|
|
|
queued = {
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"source": "payment_contract",
|
|
|
|
|
"starts_at": (now + timedelta(days=10)).isoformat(),
|
|
|
|
|
"expires_at": (now + timedelta(days=40)).isoformat(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls.append(params)
|
|
|
|
|
assert params["select"] == "user_id,plan_code,source,starts_at,expires_at"
|
|
|
|
|
assert params["status"] == "eq.active"
|
|
|
|
|
assert params["order"] == "user_id.asc,expires_at.desc"
|
|
|
|
|
return _Response(200, [queued, current])
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
result = service.list_active_subscription_windows(limit=200)
|
|
|
|
|
|
|
|
|
|
assert result["subscriptions"] == [current]
|
|
|
|
|
assert result["windows"]["user-1"]["queued_count"] == 1
|
|
|
|
|
assert calls and len(calls) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_latest_subscription_any_status_uses_cache(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = {"count": 0}
|
|
|
|
|
latest = {
|
|
|
|
|
"id": 3,
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"status": "expired",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"starts_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
"expires_at": "2026-04-01T00:00:00+00:00",
|
|
|
|
|
"created_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
"updated_at": "2026-04-01T00:00:00+00:00",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls["count"] += 1
|
|
|
|
|
assert params["user_id"] == "eq.user-1"
|
|
|
|
|
assert params["order"] == "created_at.desc"
|
|
|
|
|
assert params["select"] == "plan_code,starts_at,expires_at"
|
|
|
|
|
return _Response(200, [latest])
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.get_latest_subscription_any_status("user-1") == latest
|
|
|
|
|
assert service.get_latest_subscription_any_status("user-1") == latest
|
|
|
|
|
assert calls["count"] == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_auth_users_batches_profiles_before_admin_fallback(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = []
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls.append((url, params))
|
|
|
|
|
if url.endswith("/rest/v1/profiles"):
|
|
|
|
|
assert params["id"] == "in.(user-1,user-2)"
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"id": "user-1",
|
|
|
|
|
"email": "one@example.com",
|
|
|
|
|
"created_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"id": "user-2",
|
|
|
|
|
"email": "two@example.com",
|
|
|
|
|
"created_at": "2026-03-02T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
raise AssertionError(f"unexpected admin fallback call: {url}")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
result = service.get_auth_users(["user-1", "user-2"])
|
|
|
|
|
|
|
|
|
|
assert result == {
|
|
|
|
|
"user-1": {
|
|
|
|
|
"email": "one@example.com",
|
|
|
|
|
"created_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
"user-2": {
|
|
|
|
|
"email": "two@example.com",
|
|
|
|
|
"created_at": "2026-03-02T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
assert len(calls) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_auth_users_uses_short_cache_for_profile_results(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = {"count": 0}
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls["count"] += 1
|
|
|
|
|
assert url.endswith("/rest/v1/profiles")
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"id": "user-1",
|
|
|
|
|
"email": "one@example.com",
|
|
|
|
|
"created_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.get_auth_users(["user-1"]) == {
|
|
|
|
|
"user-1": {
|
|
|
|
|
"email": "one@example.com",
|
|
|
|
|
"created_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
assert service.get_auth_users(["user-1"]) == {
|
|
|
|
|
"user-1": {
|
|
|
|
|
"email": "one@example.com",
|
|
|
|
|
"created_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
assert calls["count"] == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_list_active_subscriptions_uses_cache_and_invalidation(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = {"count": 0}
|
|
|
|
|
row = {
|
|
|
|
|
"id": 1,
|
|
|
|
|
"user_id": "user-1",
|
|
|
|
|
"status": "active",
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"starts_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
"expires_at": "2099-04-01T00:00:00+00:00",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls["count"] += 1
|
|
|
|
|
assert params["status"] == "eq.active"
|
|
|
|
|
assert params["select"] == "user_id,plan_code,starts_at,expires_at"
|
|
|
|
|
return _Response(200, [row])
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.list_active_subscriptions(limit=200) == [row]
|
|
|
|
|
assert service.list_active_subscriptions(limit=200) == [row]
|
|
|
|
|
assert calls["count"] == 1
|
|
|
|
|
|
|
|
|
|
service.invalidate_subscription_cache("user-1")
|
|
|
|
|
|
|
|
|
|
assert service.list_active_subscriptions(limit=200) == [row]
|
|
|
|
|
assert calls["count"] == 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_has_active_subscription_uses_lightweight_query_without_polluting_detail_cache(
|
|
|
|
|
monkeypatch,
|
|
|
|
|
):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = []
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls.append(params["select"])
|
|
|
|
|
if params["select"] == "expires_at":
|
|
|
|
|
assert str(params["starts_at"]).startswith("lte.")
|
|
|
|
|
assert params["limit"] == "1"
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"expires_at": "2099-04-01T00:00:00+00:00",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
if params["select"] == "plan_code,source,starts_at,expires_at":
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"source": "payment_contract",
|
|
|
|
|
"starts_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
"expires_at": "2099-04-01T00:00:00+00:00",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
raise AssertionError(params["select"])
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.has_active_subscription("user-1", respect_requirement=False) is True
|
|
|
|
|
assert service.has_active_subscription("user-1", respect_requirement=False) is True
|
|
|
|
|
assert service.get_latest_active_subscription(
|
|
|
|
|
"user-1",
|
|
|
|
|
respect_requirement=False,
|
|
|
|
|
)["plan_code"] == "pro_monthly"
|
|
|
|
|
|
|
|
|
|
assert calls == [
|
|
|
|
|
"expires_at",
|
|
|
|
|
"plan_code,source,starts_at,expires_at",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_has_active_subscription_lightweight_cache_invalidates(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = {"count": 0}
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls["count"] += 1
|
|
|
|
|
assert params["select"] == "expires_at"
|
|
|
|
|
assert str(params["starts_at"]).startswith("lte.")
|
|
|
|
|
assert params["limit"] == "1"
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"expires_at": "2099-04-01T00:00:00+00:00",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.has_active_subscription("user-1", respect_requirement=False) is True
|
|
|
|
|
assert service.has_active_subscription("user-1", respect_requirement=False) is True
|
|
|
|
|
assert calls["count"] == 1
|
|
|
|
|
|
|
|
|
|
service.invalidate_subscription_cache("user-1")
|
|
|
|
|
|
|
|
|
|
assert service.has_active_subscription("user-1", respect_requirement=False) is True
|
|
|
|
|
assert calls["count"] == 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_has_active_subscription_reuses_detailed_subscription_cache(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = []
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls.append(params["select"])
|
|
|
|
|
assert params["select"] == "plan_code,source,starts_at,expires_at"
|
|
|
|
|
return _Response(
|
|
|
|
|
200,
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
"plan_code": "pro_monthly",
|
|
|
|
|
"source": "payment_contract",
|
|
|
|
|
"starts_at": "2026-03-01T00:00:00+00:00",
|
|
|
|
|
"expires_at": "2099-04-01T00:00:00+00:00",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.get_latest_active_subscription(
|
|
|
|
|
"user-1",
|
|
|
|
|
respect_requirement=False,
|
|
|
|
|
)["plan_code"] == "pro_monthly"
|
|
|
|
|
assert service.has_active_subscription("user-1", respect_requirement=False) is True
|
|
|
|
|
|
|
|
|
|
assert calls == ["plan_code,source,starts_at,expires_at"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_latest_active_subscription_reuses_negative_lightweight_cache(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("SUPABASE_URL", "https://example.supabase.co")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_ANON_KEY", "anon-key")
|
|
|
|
|
monkeypatch.setenv("SUPABASE_SERVICE_ROLE_KEY", "service-role")
|
|
|
|
|
|
|
|
|
|
service = SupabaseEntitlementService()
|
|
|
|
|
calls = []
|
|
|
|
|
|
|
|
|
|
def _fake_get(url, headers=None, params=None, timeout=None):
|
|
|
|
|
calls.append(params["select"])
|
|
|
|
|
assert params["select"] == "expires_at"
|
|
|
|
|
assert str(params["starts_at"]).startswith("lte.")
|
|
|
|
|
assert params["limit"] == "1"
|
|
|
|
|
return _Response(200, [])
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(entitlement_module.requests, "get", _fake_get)
|
|
|
|
|
|
|
|
|
|
assert service.has_active_subscription("user-1", respect_requirement=False) is False
|
|
|
|
|
assert service.get_latest_active_subscription(
|
|
|
|
|
"user-1",
|
|
|
|
|
respect_requirement=False,
|
|
|
|
|
) is None
|
|
|
|
|
|
|
|
|
|
assert calls == ["expires_at"]
|