Files
openswarm/backend/tests/test_settings_server_owned.py
T

126 lines
4.8 KiB
Python

"""Server-owned settings fields must survive full-object PUTs from the renderer.
Reproduces the production bug where a Settings save built from a pre-activation
snapshot (the renderer PUTs the ENTIRE AppSettings object) silently wiped
openswarm_bearer_token + connection_mode, disconnecting paying subscribers
minutes after a successful Stripe activation. The fix: subscription/identity
fields are written only by their dedicated endpoints (activate, signin-activate,
signout, disconnect); PUT /api/settings preserves whatever is on disk for them.
"""
from __future__ import annotations
import pytest
from unittest.mock import patch, AsyncMock
from fastapi.testclient import TestClient
from backend.main import app
@pytest.fixture
def client():
import backend.auth as auth_mod
if not auth_mod.TOKEN:
import secrets
auth_mod.TOKEN = secrets.token_urlsafe(32)
return TestClient(app, headers={"Authorization": f"Bearer {auth_mod.TOKEN}"})
@pytest.fixture
def reset_settings():
from backend.apps.settings.settings import load_settings, save_settings
original = load_settings().model_copy(deep=True)
yield
save_settings(original)
def p_activate_pro(client, token="repro-bearer-0123456789abcdef"):
"""Drive the real /api/subscription/activate with a mocked cloud /api/me."""
fake_me = AsyncMock()
fake_me.status_code = 200
fake_me.json = lambda: {
"email": "payer@example.com",
"plan": "pro",
"status": "active",
"current_period_end": 4102444800000,
"usage": {"utilization": 0},
}
with patch("httpx.AsyncClient") as MockClient:
instance = MockClient.return_value.__aenter__.return_value
instance.get = AsyncMock(return_value=fake_me)
r = client.post("/api/subscription/activate", json={"token": token})
assert r.status_code == 200, r.text
return token
def test_stale_settings_put_cannot_wipe_activation(client, reset_settings):
"""The exact production sequence: snapshot settings, activate Pro, PUT the
stale snapshot back (renderer Save of a pre-activation draft). The bearer
and pro mode must survive; the user's editable change must still apply."""
snapshot = client.get("/api/settings").json()
assert snapshot is not None
token = p_activate_pro(client)
from backend.apps.settings.settings import load_settings
s = load_settings()
assert s.openswarm_bearer_token == token
assert s.connection_mode == "openswarm-pro"
assert s.openswarm_subscription_plan == "pro"
stale = dict(snapshot)
stale["user_name"] = "Stale Draft Save"
r = client.put("/api/settings", json=stale)
assert r.status_code == 200
s = load_settings()
assert s.user_name == "Stale Draft Save"
assert s.openswarm_bearer_token == token, "stale PUT wiped the bearer"
assert s.connection_mode == "openswarm-pro", "stale PUT reverted connection_mode"
assert s.openswarm_subscription_plan == "pro"
assert s.openswarm_subscription_expires is not None
body = r.json()["settings"]
assert body["openswarm_bearer_token"] == token
assert body["connection_mode"] == "openswarm-pro"
def test_put_cannot_inject_server_owned_fields(client, reset_settings):
"""The inverse direction: a client PUT must not be able to SET subscription
state either (it would imply entitlement the cloud never granted)."""
snapshot = client.get("/api/settings").json()
forged = dict(snapshot)
forged["connection_mode"] = "openswarm-pro"
forged["openswarm_bearer_token"] = "forged-bearer-fedcba9876543210"
forged["openswarm_subscription_plan"] = "ultra"
forged["user_id"] = "u-forged"
r = client.put("/api/settings", json=forged)
assert r.status_code == 200
from backend.apps.settings.settings import load_settings
s = load_settings()
assert s.openswarm_bearer_token == snapshot.get("openswarm_bearer_token")
assert s.connection_mode == snapshot.get("connection_mode")
assert s.openswarm_subscription_plan == snapshot.get("openswarm_subscription_plan")
assert s.user_id == snapshot.get("user_id")
def test_dedicated_endpoints_still_mutate(client, reset_settings):
"""Freezing PUT must not freeze the real owners: disconnect still reverts
routing, and a fresh activate still re-connects afterwards."""
p_activate_pro(client)
r = client.post("/api/subscription/disconnect")
assert r.status_code == 200
from backend.apps.settings.settings import load_settings
s = load_settings()
assert s.connection_mode == "own_key"
assert s.openswarm_bearer_token is not None # disconnect keeps sign-in
p_activate_pro(client, token="second-bearer-aaaabbbbccccdddd")
s = load_settings()
assert s.connection_mode == "openswarm-pro"
assert s.openswarm_bearer_token == "second-bearer-aaaabbbbccccdddd"