shinkan-jinkendo/backend/tests/test_club_feature_m5.py
Lars 8404a42b6c
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 42s
Test Suite / playwright-tests (push) Successful in 1m19s
Implement Club Feature Quota Bypass and Update Versioning
- Added support for club feature quota bypass based on portal roles and profile grants in the capabilities check.
- Introduced new functions to handle quota bypass logic in club feature access and consumption.
- Updated the FeatureUsageBadge component to reflect platform exemptions for features.
- Incremented application version to 0.8.195 and database schema version to 20260606083 to reflect these changes.
- Enhanced backend routers to include new logic for consuming club features during AI-related actions.
2026-06-07 07:43:35 +02:00

123 lines
3.5 KiB
Python

"""M5: ai_calls Verbrauch + Hard-Block (CLUB_FEATURE_ENFORCE)."""
import pytest
from fastapi import HTTPException
from club_features import (
club_feature_enforcement_enabled,
consume_club_feature,
probe_club_feature_access,
)
def _fake_cur():
class C:
def execute(self, *a, **k):
pass
def fetchone(self):
return None
return C()
def test_probe_blocks_when_enforce_and_limit_exceeded(monkeypatch):
monkeypatch.setenv("CLUB_FEATURE_ENFORCE", "1")
monkeypatch.setattr("club_features.get_cursor", lambda conn: _fake_cur())
monkeypatch.setattr(
"club_quota_bypass.is_club_feature_quota_bypassed",
lambda *a, **k: False,
)
monkeypatch.setattr(
"club_features.check_club_feature_access",
lambda club_id, feature_id, conn=None: {
"allowed": False,
"limit": 0,
"used": 0,
"remaining": 0,
"reason": "feature_disabled",
"plan_id": "free",
},
)
monkeypatch.setattr("club_feature_logger.log_club_feature_usage", lambda **kwargs: None)
with pytest.raises(HTTPException) as exc:
probe_club_feature_access(
feature_id="ai_calls",
action="suggest",
club_id=12,
profile_id=3,
endpoint="POST /exercises/ai/suggest",
conn=object(),
)
assert exc.value.status_code == 403
assert "ai_calls" in str(exc.value.detail)
def test_probe_allows_when_enforce_off(monkeypatch):
monkeypatch.setenv("CLUB_FEATURE_ENFORCE", "0")
monkeypatch.setattr("club_features.get_cursor", lambda conn: _fake_cur())
monkeypatch.setattr(
"club_quota_bypass.is_club_feature_quota_bypassed",
lambda *a, **k: False,
)
monkeypatch.setattr(
"club_features.check_club_feature_access",
lambda club_id, feature_id, conn=None: {
"allowed": False,
"limit": 0,
"used": 0,
"remaining": 0,
"reason": "feature_disabled",
"plan_id": "free",
},
)
monkeypatch.setattr("club_feature_logger.log_club_feature_usage", lambda **kwargs: None)
access = probe_club_feature_access(
feature_id="ai_calls",
action="suggest",
club_id=12,
profile_id=3,
conn=object(),
)
assert access["allowed"] is False
def test_consume_skips_without_club_id(monkeypatch):
calls = []
def _inc(*args, **kwargs):
calls.append(1)
monkeypatch.setattr("club_features.increment_club_feature_usage", _inc)
consume_club_feature(feature_id="ai_calls", club_id=None, profile_id=1)
assert calls == []
def test_consume_increments_once_per_call(monkeypatch):
calls = []
def _inc(club_id, feature_id, **kwargs):
calls.append((club_id, feature_id, kwargs.get("action")))
monkeypatch.setattr("club_features.get_cursor", lambda conn: _fake_cur())
monkeypatch.setattr(
"club_quota_bypass.is_club_feature_quota_bypassed",
lambda *a, **k: False,
)
monkeypatch.setattr("club_features.increment_club_feature_usage", _inc)
consume_club_feature(
feature_id="ai_calls",
club_id=5,
profile_id=9,
portal_role="trainer",
action="suggest",
conn=object(),
)
assert calls == [(5, "ai_calls", "suggest")]
def test_club_feature_enforcement_env_default_off(monkeypatch):
monkeypatch.delenv("CLUB_FEATURE_ENFORCE", raising=False)
assert club_feature_enforcement_enabled() is False