"""Repeatable user, tenant-admin and platform-admin acceptance journeys. These tests exercise public WSGI routes and persisted state. Provider doubles are explicit: they prove portal orchestration, not live OTP/mail/directory behavior. """ from dataclasses import replace from html import unescape import re import unittest from urllib.parse import quote import test_portal_navigation from test_web import invoke, invoke_confirmed, FakeProvisioning from user_engine.domain import AccountStatus, OutboxEvent, utc_now from user_engine.oidc import BrowserSession from user_engine.testing.fixtures import human_actor_claims TENANT='tenant:trial:demo-company' OTHER='tenant:trial:other' class JourneyFixture(unittest.TestCase): def setUp(self): test_portal_navigation.PortalNavigationTests.setUp(self) self.app.provisioning=FakeProvisioning() claims=human_actor_claims(subject='tenant-admin',tenant=TENANT) claims['roles']=['tenant-admin'] self.oidc.sessions['admin']=BrowserSession(claims,9999999999,'admin-csrf') self.actor=self.app.service.identity_adapter.normalize(self.oidc.claims('operator')) def member(self, tenant=TENANT, role='user', email='person@example.test', linked=True): user=self.app.service.create_user(self.actor,display_name='Display name',primary_email=email) self.app.service.set_tenant_account_status(self.actor,user.user_id,AccountStatus.ACTIVE,tenant=tenant) self.app.service.add_membership(self.actor,user.user_id,tenant=tenant,scope_type='tenant',scope_id=tenant,kind=role) if linked: self.app.service.link_identity(self.actor,user.user_id,issuer='urn:netkingdom:directory', subject='uid='+email.split('@')[0]+',ou=people,dc=netkingdom,dc=local',provider='netkingdom-lldap') return user def post(self,path,who='admin',confirmed=False,**form): form.setdefault('csrf_token',who+'-csrf') call=invoke_confirmed if confirmed else invoke return call(self.app,path,method='POST',cookie='ue_session='+who,form=form) def confirm_token(self,body): return unescape(re.search(rb'name="confirm_token" value="([^"]+)"',body).group(1).decode()) class UserJourneys(JourneyFixture): def test_profile_validation_keeps_safe_input_and_retry_saves(self): invoke(self.app,'/onboarding',cookie='ue_session=member') session=self.app.service.me(self.oidc.claims('member'),correlation_id='before') original=session.user.display_name response,body=self.post('/onboarding/profile',who='member',display_name='