HttpUserEngineClient uses trusted-proxy claims against live user-engine. Offline stub when URL/secret unset. Align default tenant with KeyCape tenant:coulomb; map OIDC tenant/principal_type/groups into the envelope.
96 lines
3.2 KiB
Python
96 lines
3.2 KiB
Python
import json
|
|
|
|
import httpx
|
|
import pytest
|
|
from django.test import override_settings
|
|
|
|
from coulomb_social.apps.identity.user_engine import (
|
|
HttpUserEngineClient,
|
|
IdentityClaims,
|
|
StubUserEngineClient,
|
|
get_user_engine_client,
|
|
)
|
|
|
|
|
|
def test_stub_is_deterministic():
|
|
c = StubUserEngineClient()
|
|
claims = IdentityClaims(issuer="https://iss", subject="sub-1", name="A")
|
|
a = c.link_or_create(claims, tenant_id="tenant:coulomb", application_id="coulomb-social")
|
|
b = c.link_or_create(claims, tenant_id="tenant:coulomb", application_id="coulomb-social")
|
|
assert a.user_id == b.user_id
|
|
assert a.source == "stub"
|
|
|
|
|
|
def test_http_client_calls_me(httpx_mock=None):
|
|
"""Manual transport mock without pytest-httpx plugin."""
|
|
claims = IdentityClaims(
|
|
issuer="https://kc.coulomb.social",
|
|
subject="sub-http",
|
|
name="Http User",
|
|
email="h@example.com",
|
|
tenant="tenant:coulomb",
|
|
principal_type="human",
|
|
roles=("user",),
|
|
authorized_party="coulomb-social",
|
|
)
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
assert request.url.path == "/api/v1/me"
|
|
assert request.headers["X-User-Engine-Proxy-Secret"] == "secret-value-at-least-24-chars"
|
|
raw = json.loads(request.headers["X-Verified-Oidc-Claims"])
|
|
assert raw["iss"] == claims.issuer
|
|
assert raw["sub"] == claims.subject
|
|
assert raw["tenant"] == "tenant:coulomb"
|
|
assert "user-engine-portal" in raw["aud"]
|
|
assert "coulomb-social" in raw["aud"]
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"user": {
|
|
"user_id": "usr_test_123",
|
|
"display_name": "Http User",
|
|
"primary_email": "h@example.com",
|
|
},
|
|
"actor": {"tenant": "tenant:coulomb", "subject": claims.subject},
|
|
"account": {},
|
|
"identities": [],
|
|
},
|
|
)
|
|
|
|
transport = httpx.MockTransport(handler)
|
|
client = HttpUserEngineClient("https://users.example.test", "secret-value-at-least-24-chars")
|
|
|
|
# inject transport by patching Client used inside method
|
|
import coulomb_social.apps.identity.user_engine as ue
|
|
|
|
real_client = httpx.Client
|
|
|
|
def client_factory(*args, **kwargs):
|
|
kwargs["transport"] = transport
|
|
return real_client(*args, **kwargs)
|
|
|
|
original = ue.httpx.Client
|
|
ue.httpx.Client = client_factory # type: ignore[misc]
|
|
try:
|
|
link = client.link_or_create(
|
|
claims, tenant_id="tenant:coulomb", application_id="coulomb-social"
|
|
)
|
|
finally:
|
|
ue.httpx.Client = original # type: ignore[misc]
|
|
|
|
assert link.user_id == "usr_test_123"
|
|
assert link.source == "http"
|
|
assert link.display_name == "Http User"
|
|
|
|
|
|
@override_settings(USER_ENGINE_BASE_URL="", USER_ENGINE_PROXY_SECRET="")
|
|
def test_get_client_stub_when_unconfigured():
|
|
assert isinstance(get_user_engine_client(), StubUserEngineClient)
|
|
|
|
|
|
@override_settings(
|
|
USER_ENGINE_BASE_URL="https://users.example.test",
|
|
USER_ENGINE_PROXY_SECRET="secret-value-at-least-24-chars",
|
|
)
|
|
def test_get_client_http_when_configured():
|
|
assert isinstance(get_user_engine_client(), HttpUserEngineClient)
|