feat: wire license.effective_tier into tiers.py; add dev_override priority
This commit is contained in:
parent
52f912f938
commit
5739d1935b
3 changed files with 99 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -29,3 +29,4 @@ scrapers/.debug/
|
|||
scrapers/raw_scrapes/
|
||||
|
||||
compose.override.yml
|
||||
config/license.json
|
||||
|
|
|
|||
|
|
@ -65,3 +65,32 @@ def tier_label(feature: str) -> str:
|
|||
if required is None:
|
||||
return ""
|
||||
return "🔒 Paid" if required == "paid" else "⭐ Premium"
|
||||
|
||||
|
||||
def effective_tier(
|
||||
profile=None,
|
||||
license_path=None,
|
||||
public_key_path=None,
|
||||
) -> str:
|
||||
"""Return the effective tier for this installation.
|
||||
|
||||
Priority:
|
||||
1. profile.dev_tier_override (developer mode override)
|
||||
2. License JWT verification (offline RS256 check)
|
||||
3. "free" (fallback)
|
||||
|
||||
license_path and public_key_path default to production paths when None.
|
||||
Pass explicit paths in tests to avoid touching real files.
|
||||
"""
|
||||
if profile and getattr(profile, "dev_tier_override", None):
|
||||
return profile.dev_tier_override
|
||||
|
||||
from scripts.license import effective_tier as _license_tier
|
||||
from pathlib import Path as _Path
|
||||
|
||||
kwargs = {}
|
||||
if license_path is not None:
|
||||
kwargs["license_path"] = _Path(license_path)
|
||||
if public_key_path is not None:
|
||||
kwargs["public_key_path"] = _Path(public_key_path)
|
||||
return _license_tier(**kwargs)
|
||||
|
|
|
|||
69
tests/test_license_tier_integration.py
Normal file
69
tests/test_license_tier_integration.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import json
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import patch
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
import jwt as pyjwt
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def license_env(tmp_path):
|
||||
"""Returns (private_pem, public_path, license_path) for tier integration tests."""
|
||||
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
private_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
public_pem = private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
public_path = tmp_path / "public.pem"
|
||||
public_path.write_bytes(public_pem)
|
||||
license_path = tmp_path / "license.json"
|
||||
return private_pem, public_path, license_path
|
||||
|
||||
|
||||
def _write_jwt_license(license_path, private_pem, tier="paid", days=30):
|
||||
now = datetime.now(timezone.utc)
|
||||
token = pyjwt.encode({
|
||||
"sub": "CFG-PRNG-TEST", "product": "peregrine", "tier": tier,
|
||||
"iat": now, "exp": now + timedelta(days=days),
|
||||
}, private_pem, algorithm="RS256")
|
||||
license_path.write_text(json.dumps({"jwt": token, "grace_until": None}))
|
||||
|
||||
|
||||
def test_effective_tier_free_without_license(tmp_path):
|
||||
from app.wizard.tiers import effective_tier
|
||||
tier = effective_tier(
|
||||
profile=None,
|
||||
license_path=tmp_path / "missing.json",
|
||||
public_key_path=tmp_path / "key.pem",
|
||||
)
|
||||
assert tier == "free"
|
||||
|
||||
|
||||
def test_effective_tier_paid_with_valid_license(license_env):
|
||||
private_pem, public_path, license_path = license_env
|
||||
_write_jwt_license(license_path, private_pem, tier="paid")
|
||||
from app.wizard.tiers import effective_tier
|
||||
tier = effective_tier(profile=None, license_path=license_path,
|
||||
public_key_path=public_path)
|
||||
assert tier == "paid"
|
||||
|
||||
|
||||
def test_effective_tier_dev_override_takes_precedence(license_env):
|
||||
"""dev_tier_override wins even when a valid license is present."""
|
||||
private_pem, public_path, license_path = license_env
|
||||
_write_jwt_license(license_path, private_pem, tier="paid")
|
||||
|
||||
class FakeProfile:
|
||||
dev_tier_override = "premium"
|
||||
|
||||
from app.wizard.tiers import effective_tier
|
||||
tier = effective_tier(profile=FakeProfile(), license_path=license_path,
|
||||
public_key_path=public_path)
|
||||
assert tier == "premium"
|
||||
Loading…
Reference in a new issue