coulomb-social/tests/test_user_engine.py
tegwick d88767f05b 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.
2026-08-09 01:56:44 +02:00

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)