from tests.unit.manager.provisioning_helpers import * def test_provisioning_filters_flag_groups(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="http://mailu", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="nextcloud", nextcloud_mail_sync_cronjob="nextcloud-mail-sync", provision_retry_cooldown_sec=0.0, default_user_groups=["dev"], allowed_flag_groups=["demo", "test"], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) admin = DummyAdmin() monkeypatch.setattr(prov, "keycloak_admin", admin) monkeypatch.setattr(prov.mailu, "sync", lambda reason, force=False: None) monkeypatch.setattr(prov.mailu, "wait_for_mailbox", lambda email, timeout: True) monkeypatch.setattr(prov.nextcloud, "sync_mail", lambda username, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.wger, "sync_user", lambda username, email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.firefly, "sync_user", lambda email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.vaultwarden, "invite_user", lambda email: VaultwardenInvite(True, "invited")) monkeypatch.setattr(prov.ProvisioningManager, "_all_tasks_ok", lambda *args, **kwargs: True) monkeypatch.setattr(prov.ProvisioningManager, "_send_welcome_email", lambda *args, **kwargs: None) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": None, "status": "approved", "initial_password": None, "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": ["demo", "admin"], } db = DummyDB(row) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) manager.provision_access_request("REQ123") assert "dev" in admin.groups assert "demo" in admin.groups assert "admin" not in admin.groups def test_provisioning_creates_user_and_password(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="http://mailu", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="nextcloud", nextcloud_mail_sync_cronjob="nextcloud-mail-sync", provision_retry_cooldown_sec=0.0, default_user_groups=["dev"], allowed_flag_groups=["demo"], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) class Admin(DummyAdmin): def __init__(self): super().__init__() self.created_payload = None self.reset_calls = [] def find_user(self, username): return None def get_user(self, user_id): return { "id": user_id, "username": "alice", "requiredActions": ["CONFIGURE_TOTP"], "attributes": {}, } def create_user(self, payload): self.created_payload = payload return "user-123" def update_user(self, user_id, payload): return None def reset_password(self, user_id, password, temporary=False): self.reset_calls.append((user_id, temporary)) admin = Admin() monkeypatch.setattr(prov, "keycloak_admin", admin) monkeypatch.setattr(prov.mailu, "sync", lambda reason, force=False: None) monkeypatch.setattr(prov.mailu, "wait_for_mailbox", lambda email, timeout: True) monkeypatch.setattr(prov.nextcloud, "sync_mail", lambda username, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.wger, "sync_user", lambda username, email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.firefly, "sync_user", lambda email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.vaultwarden, "invite_user", lambda email: VaultwardenInvite(True, "invited")) monkeypatch.setattr(prov.ProvisioningManager, "_all_tasks_ok", lambda *args, **kwargs: True) monkeypatch.setattr(prov.ProvisioningManager, "_send_welcome_email", lambda *args, **kwargs: None) row = { "username": "alice", "first_name": "Alice", "last_name": "Atlas", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "approved", "initial_password": None, "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": ["demo"], } db = DummyDB(row) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ124") assert outcome.status == "awaiting_onboarding" assert admin.created_payload is not None assert admin.created_payload.get("firstName") == "Alice" assert admin.created_payload.get("lastName") == "Atlas" assert admin.reset_calls def test_provisioning_create_user_fallback(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="http://mailu", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="nextcloud", nextcloud_mail_sync_cronjob="nextcloud-mail-sync", provision_retry_cooldown_sec=0.0, default_user_groups=["dev"], allowed_flag_groups=["demo"], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) class Admin(DummyAdmin): def __init__(self): super().__init__() self.find_calls = 0 self.reset_calls = [] def find_user(self, username): self.find_calls += 1 if self.find_calls >= 2: return {"id": "user-123", "username": username, "attributes": {}, "requiredActions": []} return None def get_user(self, user_id): return {"id": user_id, "username": "alice", "attributes": {}, "requiredActions": []} def create_user(self, payload): raise RuntimeError("boom") def reset_password(self, user_id, password, temporary=False): self.reset_calls.append((user_id, temporary)) admin = Admin() monkeypatch.setattr(prov, "keycloak_admin", admin) monkeypatch.setattr(prov.mailu, "sync", lambda reason, force=False: None) monkeypatch.setattr(prov.mailu, "wait_for_mailbox", lambda email, timeout: True) monkeypatch.setattr(prov.nextcloud, "sync_mail", lambda username, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.wger, "sync_user", lambda username, email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.firefly, "sync_user", lambda email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.vaultwarden, "invite_user", lambda email: VaultwardenInvite(True, "invited")) monkeypatch.setattr(prov.ProvisioningManager, "_all_tasks_ok", lambda *args, **kwargs: True) monkeypatch.setattr(prov.ProvisioningManager, "_send_welcome_email", lambda *args, **kwargs: None) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "approved", "initial_password": None, "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": ["demo"], } db = DummyDB(row) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ125") assert outcome.status == "awaiting_onboarding" assert admin.find_calls >= 2 assert admin.reset_calls def test_extract_attr_variants() -> None: assert prov._extract_attr("bad", "key") == "" assert prov._extract_attr({"key": ["value"]}, "key") == "value" assert prov._extract_attr({"key": ["", " "]}, "key") == "" assert prov._extract_attr({"key": "value"}, "key") == "value" assert prov._extract_attr({}, "key") == "" def test_provisioning_empty_request_code(monkeypatch) -> None: db = DummyDB({}) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("") assert outcome.status == "unknown" def test_provisioning_admin_not_ready(monkeypatch) -> None: monkeypatch.setattr(prov.keycloak_admin, "ready", lambda: False) db = DummyDB({}) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ") assert outcome.status == "accounts_building" def test_provisioning_lock_not_acquired(monkeypatch) -> None: monkeypatch.setattr(prov.keycloak_admin, "ready", lambda: True) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "approved", "initial_password": None, "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": [], } db = DummyDB(row, locked=False) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ") assert outcome.status == "accounts_building" def test_provisioning_cooldown_short_circuit(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="http://mailu", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="nextcloud", nextcloud_mail_sync_cronjob="nextcloud-mail-sync", provision_retry_cooldown_sec=9999.0, default_user_groups=["dev"], allowed_flag_groups=["demo"], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", provision_poll_interval_sec=1.0, ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) monkeypatch.setattr(prov.keycloak_admin, "ready", lambda: True) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "accounts_building", "initial_password": None, "initial_password_revealed_at": None, "provision_attempted_at": datetime.now(timezone.utc), "approval_flags": [], } db = DummyDB(row, locked=True) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ") assert outcome.status == "accounts_building" def test_provisioning_mailu_sync_disabled(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="", nextcloud_mail_sync_cronjob="", provision_retry_cooldown_sec=0.0, default_user_groups=["dev"], allowed_flag_groups=["demo"], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) class Admin(DummyAdmin): def __init__(self): super().__init__() self.updated_actions = [] def find_user(self, username): return {"id": "user-1"} def get_user(self, user_id): return { "id": user_id, "username": "alice", "requiredActions": ["CONFIGURE_TOTP"], "attributes": { "mailu_email": ["alice@bstein.dev"], "mailu_enabled": ["false"], "wger_password": ["pw"], "wger_password_updated_at": ["done"], "firefly_password": ["pw"], "firefly_password_updated_at": ["done"], }, } def update_user_safe(self, user_id, payload): self.updated_actions.append(payload) admin = Admin() monkeypatch.setattr(prov, "keycloak_admin", admin) monkeypatch.setattr(prov.wger, "sync_user", lambda username, email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.firefly, "sync_user", lambda email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.vaultwarden, "invite_user", lambda email: VaultwardenInvite(True, "invited")) monkeypatch.setattr(prov.ProvisioningManager, "_all_tasks_ok", lambda *args, **kwargs: False) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "accounts_building", "initial_password": "temp", "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": [], } db = DummyDB(row) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ125") assert outcome.status == "accounts_building" assert admin.updated_actions def test_provisioning_sets_missing_email(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="http://mailu", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="nextcloud", nextcloud_mail_sync_cronjob="nextcloud-mail-sync", provision_retry_cooldown_sec=0.0, default_user_groups=["dev"], allowed_flag_groups=[], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) class Admin(DummyAdmin): def __init__(self): super().__init__() self.updated_actions = [] def find_user(self, username): return {"id": "user-1"} def get_user(self, user_id): return {"id": user_id, "username": "alice", "email": None, "attributes": {}} def update_user_safe(self, user_id, payload): self.updated_actions.append(payload) admin = Admin() monkeypatch.setattr(prov, "keycloak_admin", admin) monkeypatch.setattr(prov.mailu, "sync", lambda reason, force=False: None) monkeypatch.setattr(prov.mailu, "wait_for_mailbox", lambda email, timeout: True) monkeypatch.setattr(prov.nextcloud, "sync_mail", lambda username, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.wger, "sync_user", lambda username, email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.firefly, "sync_user", lambda email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.vaultwarden, "invite_user", lambda email: VaultwardenInvite(True, "invited")) monkeypatch.setattr(prov.ProvisioningManager, "_all_tasks_ok", lambda *args, **kwargs: False) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "accounts_building", "initial_password": "temp", "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": [], } db = DummyDB(row) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ126") assert outcome.status == "accounts_building" assert any("email" in payload for payload in admin.updated_actions) def test_provisioning_mailbox_not_ready(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="http://mailu", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="nextcloud", nextcloud_mail_sync_cronjob="nextcloud-mail-sync", provision_retry_cooldown_sec=0.0, default_user_groups=["dev"], allowed_flag_groups=[], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) admin = DummyAdmin() monkeypatch.setattr(prov, "keycloak_admin", admin) monkeypatch.setattr(prov.mailu, "sync", lambda reason, force=False: None) monkeypatch.setattr(prov.mailu, "wait_for_mailbox", lambda email, timeout: False) monkeypatch.setattr(prov.nextcloud, "sync_mail", lambda username, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.wger, "sync_user", lambda username, email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.firefly, "sync_user", lambda email, password, wait=True: {"status": "ok"}) monkeypatch.setattr(prov.vaultwarden, "invite_user", lambda email: VaultwardenInvite(True, "invited")) monkeypatch.setattr(prov.ProvisioningManager, "_all_tasks_ok", lambda *args, **kwargs: False) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "approved", "initial_password": "temp", "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": [], } db = DummyDB(row) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ126") assert outcome.status == "accounts_building" def test_provisioning_sync_errors(monkeypatch) -> None: dummy_settings = types.SimpleNamespace( mailu_domain="bstein.dev", mailu_sync_url="http://mailu", mailu_mailbox_wait_timeout_sec=1.0, nextcloud_namespace="nextcloud", nextcloud_mail_sync_cronjob="nextcloud-mail-sync", provision_retry_cooldown_sec=0.0, default_user_groups=["dev"], allowed_flag_groups=[], welcome_email_enabled=False, portal_public_base_url="https://bstein.dev", ) monkeypatch.setattr(prov, "settings", dummy_settings) _patch_mailu_ready(monkeypatch, dummy_settings) admin = DummyAdmin() monkeypatch.setattr(prov, "keycloak_admin", admin) monkeypatch.setattr(prov.mailu, "sync", lambda reason, force=False: None) monkeypatch.setattr(prov.mailu, "wait_for_mailbox", lambda email, timeout: True) monkeypatch.setattr(prov.nextcloud, "sync_mail", lambda username, wait=True: {"status": "error"}) monkeypatch.setattr(prov.wger, "sync_user", lambda username, email, password, wait=True: {"status": "error"}) monkeypatch.setattr(prov.firefly, "sync_user", lambda email, password, wait=True: {"status": "error"}) monkeypatch.setattr(prov.vaultwarden, "invite_user", lambda email: VaultwardenInvite(False, "error", "fail")) monkeypatch.setattr(prov.ProvisioningManager, "_all_tasks_ok", lambda *args, **kwargs: False) row = { "username": "alice", "contact_email": "alice@example.com", "email_verified_at": datetime.now(timezone.utc), "status": "approved", "initial_password": "temp", "initial_password_revealed_at": None, "provision_attempted_at": None, "approval_flags": [], } db = DummyDB(row) storage = DummyStorage() manager = prov.ProvisioningManager(db, storage) outcome = manager.provision_access_request("REQ127") assert outcome.status == "accounts_building"