feat(api): enforce ownership across health routines and profile flows
This commit is contained in:
parent
cd8e39939a
commit
ffa3b71309
14 changed files with 1225 additions and 206 deletions
112
backend/tests/test_routines_auth.py
Normal file
112
backend/tests/test_routines_auth.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
|
||||
from innercontext.api.auth_deps import get_current_user
|
||||
from innercontext.auth import CurrentUser, IdentityData, TokenClaims
|
||||
from innercontext.models import Role
|
||||
from main import app
|
||||
|
||||
|
||||
def _user(subject: str, *, role: Role = Role.MEMBER) -> CurrentUser:
|
||||
claims = TokenClaims(
|
||||
issuer="https://auth.test",
|
||||
subject=subject,
|
||||
audience=("innercontext-web",),
|
||||
expires_at=datetime.now(UTC) + timedelta(hours=1),
|
||||
raw_claims={"iss": "https://auth.test", "sub": subject},
|
||||
)
|
||||
return CurrentUser(
|
||||
user_id=uuid4(),
|
||||
role=role,
|
||||
identity=IdentityData.from_claims(claims),
|
||||
claims=claims,
|
||||
)
|
||||
|
||||
|
||||
def _set_current_user(user: CurrentUser) -> None:
|
||||
app.dependency_overrides[get_current_user] = lambda: user
|
||||
|
||||
|
||||
def test_suggest_uses_current_user_profile_and_visible_products_only(client):
|
||||
owner = _user("owner")
|
||||
other = _user("other")
|
||||
|
||||
_set_current_user(owner)
|
||||
owner_profile = client.patch(
|
||||
"/profile", json={"birth_date": "1991-01-15", "sex_at_birth": "male"}
|
||||
)
|
||||
owner_product = client.post(
|
||||
"/products",
|
||||
json={
|
||||
"name": "Owner Serum",
|
||||
"brand": "Test",
|
||||
"category": "serum",
|
||||
"recommended_time": "both",
|
||||
"leave_on": True,
|
||||
},
|
||||
)
|
||||
assert owner_profile.status_code == 200
|
||||
assert owner_product.status_code == 201
|
||||
|
||||
_set_current_user(other)
|
||||
other_profile = client.patch(
|
||||
"/profile", json={"birth_date": "1975-06-20", "sex_at_birth": "female"}
|
||||
)
|
||||
other_product = client.post(
|
||||
"/products",
|
||||
json={
|
||||
"name": "Other Serum",
|
||||
"brand": "Test",
|
||||
"category": "serum",
|
||||
"recommended_time": "both",
|
||||
"leave_on": True,
|
||||
},
|
||||
)
|
||||
assert other_profile.status_code == 200
|
||||
assert other_product.status_code == 201
|
||||
|
||||
_set_current_user(owner)
|
||||
|
||||
with patch(
|
||||
"innercontext.api.routines.call_gemini_with_function_tools"
|
||||
) as mock_gemini:
|
||||
mock_response = type(
|
||||
"Response",
|
||||
(),
|
||||
{
|
||||
"text": '{"steps": [{"product_id": null, "action_type": "shaving_razor"}], "reasoning": "ok", "summary": {"primary_goal": "safe", "constraints_applied": [], "confidence": 0.7}}'
|
||||
},
|
||||
)
|
||||
mock_gemini.return_value = (mock_response, None)
|
||||
|
||||
response = client.post(
|
||||
"/routines/suggest",
|
||||
json={
|
||||
"routine_date": "2026-03-05",
|
||||
"part_of_day": "am",
|
||||
"include_minoxidil_beard": False,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
kwargs = mock_gemini.call_args.kwargs
|
||||
prompt = kwargs["contents"]
|
||||
assert "Birth date: 1991-01-15" in prompt
|
||||
assert "Birth date: 1975-06-20" not in prompt
|
||||
assert "Owner Serum" in prompt
|
||||
assert "Other Serum" not in prompt
|
||||
|
||||
handler = kwargs["function_handlers"]["get_product_details"]
|
||||
payload = handler(
|
||||
{
|
||||
"product_ids": [
|
||||
owner_product.json()["id"],
|
||||
other_product.json()["id"],
|
||||
]
|
||||
}
|
||||
)
|
||||
assert len(payload["products"]) == 1
|
||||
assert payload["products"][0]["name"] == "Owner Serum"
|
||||
Loading…
Add table
Add a link
Reference in a new issue