"""Unit tests for OIDC claim assembly (no live issuer).""" from __future__ import annotations from unittest.mock import patch import pytest from django.test import override_settings from coulomb_social.apps.identity import oidc @override_settings( OIDC_ENABLED=True, OIDC_ISSUER="https://kc.example.test", OIDC_CLIENT_ID="coulomb-social", OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/", ) def test_claims_prefer_id_token_when_userinfo_empty(): token = {"id_token": "header.payload.sig"} fake_claims = { "iss": "https://kc.example.test", "sub": "platform-root", "aud": "coulomb-social", "name": "Platform Root", "preferred_username": "platform-root", "tenant": "tenant:coulomb", } with patch.object(oidc, "decode_id_token", return_value=fake_claims) as dec: out = oidc.claims_from_token_response(token, {}) dec.assert_called_once_with("header.payload.sig") assert out["sub"] == "platform-root" assert out["name"] == "Platform Root" @override_settings( OIDC_ENABLED=True, OIDC_ISSUER="https://kc.example.test", OIDC_CLIENT_ID="coulomb-social", OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/", ) def test_userinfo_overlays_id_token(): token = {"id_token": "h.p.s"} with patch.object( oidc, "decode_id_token", return_value={"sub": "u1", "name": "From Token", "email": ""}, ): out = oidc.claims_from_token_response( token, {"name": "From Userinfo", "email": "a@b.c"} ) assert out["sub"] == "u1" assert out["name"] == "From Userinfo" assert out["email"] == "a@b.c" @override_settings( OIDC_ENABLED=True, OIDC_ISSUER="https://kc.example.test", OIDC_CLIENT_ID="coulomb-social", OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/", ) def test_missing_sub_raises(): with patch.object(oidc, "decode_id_token", return_value={}): with pytest.raises(oidc.OIDCConfigurationError, match="no subject"): oidc.claims_from_token_response({"id_token": "h.p.s"}, {}) @override_settings( OIDC_ENABLED=True, OIDC_ISSUER="https://kc.example.test", OIDC_CLIENT_ID="coulomb-social", OIDC_REDIRECT_URI="http://127.0.0.1:8008/auth/callback/", ) def test_fetch_userinfo_soft_fails_on_401(httpx_mock=None): import httpx class FakeResp: status_code = 401 def json(self): return {"error": "invalid_token"} with ( patch.object( oidc, "discovery_document", return_value={"userinfo_endpoint": "https://kc.example.test/userinfo"}, ), patch.object(httpx, "get", return_value=FakeResp()), ): assert oidc.fetch_userinfo("opaque-or-jwt") == {} @override_settings( OIDC_ENABLED=True, OIDC_ISSUER="https://kc.example.test", OIDC_CLIENT_ID="coulomb-social", OIDC_REDIRECT_URI="https://coulomb.example.test/auth/callback/", ) def test_authorization_url_can_request_aal2(): with patch.object( oidc, "discovery_document", return_value={"authorization_endpoint": "https://kc.example.test/authorize"}, ): url = oidc.build_authorization_url( state="state", code_verifier="verifier", acr_values="aal2" ) assert "acr_values=aal2" in url