Wire user-engine HTTP /me for member provisioning (CSOC-WP-0002-T04)
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.
This commit is contained in:
parent
a6a380b19f
commit
d88767f05b
13 changed files with 325 additions and 48 deletions
96
tests/test_user_engine.py
Normal file
96
tests/test_user_engine.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue