2026-01-19 19:01:32 -03:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-01-20 18:11:02 -03:00
|
|
|
import time
|
2026-01-19 19:01:32 -03:00
|
|
|
import types
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from ariadne.services.firefly import FireflyService
|
2026-01-19 23:27:10 -03:00
|
|
|
from ariadne.services.mailu import MailuService
|
2026-01-19 19:01:32 -03:00
|
|
|
from ariadne.services.nextcloud import NextcloudService
|
|
|
|
|
from ariadne.services.wger import WgerService
|
2026-01-20 00:06:50 -03:00
|
|
|
from ariadne.services.vaultwarden import VaultwardenService
|
2026-01-19 19:01:32 -03:00
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
class DummyExecutor:
|
|
|
|
|
def __init__(self, stdout: str = "ok", stderr: str = "", exit_code: int = 0):
|
2026-01-19 19:01:32 -03:00
|
|
|
self.calls = []
|
2026-01-20 23:03:04 -03:00
|
|
|
self._stdout = stdout
|
|
|
|
|
self._stderr = stderr
|
|
|
|
|
self._exit_code = exit_code
|
2026-01-19 19:01:32 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def exec(self, command, env=None, timeout_sec=None, check=True):
|
|
|
|
|
self.calls.append((command, env, timeout_sec, check))
|
|
|
|
|
return types.SimpleNamespace(
|
|
|
|
|
stdout=self._stdout,
|
|
|
|
|
stderr=self._stderr,
|
|
|
|
|
exit_code=self._exit_code,
|
|
|
|
|
ok=self._exit_code == 0,
|
|
|
|
|
)
|
2026-01-19 19:01:32 -03:00
|
|
|
|
|
|
|
|
|
2026-01-20 00:06:50 -03:00
|
|
|
class DummyResponse:
|
|
|
|
|
def __init__(self, status_code=200, text=""):
|
|
|
|
|
self.status_code = status_code
|
|
|
|
|
self.text = text
|
|
|
|
|
|
|
|
|
|
def raise_for_status(self):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DummyVaultwardenClient:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.calls = []
|
2026-01-20 18:11:02 -03:00
|
|
|
self.responses = {}
|
2026-01-20 00:06:50 -03:00
|
|
|
|
|
|
|
|
def post(self, path, json=None, data=None):
|
|
|
|
|
self.calls.append((path, json, data))
|
2026-01-20 18:11:02 -03:00
|
|
|
resp = self.responses.get(path)
|
|
|
|
|
if resp is None:
|
|
|
|
|
resp = DummyResponse(200, "")
|
|
|
|
|
return resp
|
2026-01-20 00:06:50 -03:00
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def test_nextcloud_sync_mail_no_user(monkeypatch) -> None:
|
2026-01-19 19:01:32 -03:00
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
nextcloud_namespace="nextcloud",
|
|
|
|
|
nextcloud_mail_sync_cronjob="nextcloud-mail-sync",
|
|
|
|
|
nextcloud_mail_sync_wait_timeout_sec=90.0,
|
|
|
|
|
nextcloud_mail_sync_job_ttl_sec=3600,
|
2026-01-20 23:03:04 -03:00
|
|
|
nextcloud_pod_label="app=nextcloud",
|
|
|
|
|
nextcloud_container="nextcloud",
|
|
|
|
|
nextcloud_exec_timeout_sec=30.0,
|
|
|
|
|
nextcloud_db_host="",
|
|
|
|
|
nextcloud_db_port=5432,
|
|
|
|
|
nextcloud_db_name="nextcloud",
|
|
|
|
|
nextcloud_db_user="nextcloud",
|
|
|
|
|
nextcloud_db_password="",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
mailu_host="mail.bstein.dev",
|
2026-01-19 19:01:32 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.keycloak_admin.ready", lambda: True)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.keycloak_admin.find_user", lambda *_args, **_kwargs: None)
|
2026-01-19 19:01:32 -03:00
|
|
|
|
|
|
|
|
svc = NextcloudService()
|
|
|
|
|
result = svc.sync_mail("alice", wait=True)
|
|
|
|
|
assert result["status"] == "ok"
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def test_wger_sync_user_exec(monkeypatch) -> None:
|
2026-01-19 19:01:32 -03:00
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="health",
|
|
|
|
|
wger_user_sync_cronjob="wger-user-sync",
|
|
|
|
|
wger_admin_cronjob="wger-admin-ensure",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="admin",
|
|
|
|
|
wger_admin_password="pw",
|
|
|
|
|
wger_admin_email="admin@bstein.dev",
|
2026-01-19 19:01:32 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
|
|
|
|
|
calls: list[dict[str, str]] = []
|
|
|
|
|
|
|
|
|
|
class DummyExecutor:
|
|
|
|
|
def exec(self, _cmd, env=None, timeout_sec=None, check=True):
|
|
|
|
|
calls.append(env or {})
|
|
|
|
|
return types.SimpleNamespace(stdout="ok", stderr="", exit_code=0, ok=True)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-19 19:01:32 -03:00
|
|
|
|
|
|
|
|
svc = WgerService()
|
|
|
|
|
result = svc.sync_user("alice", "alice@bstein.dev", "pw", wait=True)
|
|
|
|
|
assert result["status"] == "ok"
|
2026-01-20 23:03:04 -03:00
|
|
|
assert calls[0]["WGER_USERNAME"] == "alice"
|
2026-01-19 19:01:32 -03:00
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def test_wger_ensure_admin_exec(monkeypatch) -> None:
|
2026-01-20 18:11:02 -03:00
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="health",
|
|
|
|
|
wger_user_sync_cronjob="wger-user-sync",
|
|
|
|
|
wger_admin_cronjob="wger-admin-ensure",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="admin",
|
|
|
|
|
wger_admin_password="pw",
|
|
|
|
|
wger_admin_email="admin@bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
calls: list[dict[str, str]] = []
|
2026-01-20 18:11:02 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
class DummyExecutor:
|
|
|
|
|
def exec(self, _cmd, env=None, timeout_sec=None, check=True):
|
|
|
|
|
calls.append(env or {})
|
|
|
|
|
return types.SimpleNamespace(stdout="ok", stderr="", exit_code=0, ok=True)
|
2026-01-19 19:01:32 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.wger.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-19 19:01:32 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
svc = WgerService()
|
|
|
|
|
result = svc.ensure_admin(wait=False)
|
2026-01-19 19:01:32 -03:00
|
|
|
assert result["status"] == "ok"
|
2026-01-20 23:03:04 -03:00
|
|
|
assert calls[0]["WGER_ADMIN_USERNAME"] == "admin"
|
2026-01-19 19:01:32 -03:00
|
|
|
|
|
|
|
|
|
2026-01-21 02:57:06 -03:00
|
|
|
def test_wger_sync_users(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="health",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
|
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="admin",
|
|
|
|
|
wger_admin_password="pw",
|
|
|
|
|
wger_admin_email="admin@bstein.dev",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
|
|
|
|
|
|
|
|
|
calls: list[tuple[str, str, str]] = []
|
|
|
|
|
|
|
|
|
|
class DummyAdmin:
|
|
|
|
|
def ready(self) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def iter_users(self, page_size=200, brief=False):
|
|
|
|
|
return [{"id": "1", "username": "alice", "attributes": {}}]
|
|
|
|
|
|
|
|
|
|
def get_user(self, user_id: str):
|
|
|
|
|
return {"id": user_id, "username": "alice", "attributes": {}}
|
|
|
|
|
|
|
|
|
|
def set_user_attribute(self, username: str, key: str, value: str) -> None:
|
|
|
|
|
calls.append((username, key, value))
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.keycloak_admin", DummyAdmin())
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.wger.mailu.resolve_mailu_email",
|
|
|
|
|
lambda *_args, **_kwargs: "alice@bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.random_password", lambda *_args: "pw")
|
|
|
|
|
|
|
|
|
|
def fake_sync_user(self, *_args, **_kwargs):
|
|
|
|
|
return {"status": "ok", "detail": "ok"}
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(WgerService, "sync_user", fake_sync_user)
|
|
|
|
|
|
|
|
|
|
svc = WgerService()
|
|
|
|
|
result = svc.sync_users()
|
|
|
|
|
assert result["status"] == "ok"
|
|
|
|
|
assert any(key == "wger_password" for _user, key, _value in calls)
|
|
|
|
|
assert any(key == "wger_password_updated_at" for _user, key, _value in calls)
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def test_firefly_sync_user_exec(monkeypatch) -> None:
|
2026-01-20 18:11:02 -03:00
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
firefly_namespace="finance",
|
|
|
|
|
firefly_user_sync_cronjob="firefly-user-sync",
|
|
|
|
|
firefly_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
firefly_pod_label="app=firefly",
|
|
|
|
|
firefly_container="firefly",
|
|
|
|
|
firefly_cron_base_url="http://firefly/cron",
|
|
|
|
|
firefly_cron_token="token",
|
|
|
|
|
firefly_cron_timeout_sec=10.0,
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
|
|
|
|
|
class DummyExecutor:
|
|
|
|
|
def exec(self, _cmd, env=None, timeout_sec=None, check=True):
|
|
|
|
|
return types.SimpleNamespace(stdout="ok", stderr="", exit_code=0, ok=True)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = FireflyService()
|
2026-01-20 23:03:04 -03:00
|
|
|
result = svc.sync_user("alice@bstein.dev", "pw", wait=True)
|
|
|
|
|
assert result["status"] == "ok"
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_firefly_sync_missing_inputs(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
firefly_namespace="finance",
|
|
|
|
|
firefly_user_sync_cronjob="firefly-user-sync",
|
|
|
|
|
firefly_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
firefly_pod_label="app=firefly",
|
|
|
|
|
firefly_container="firefly",
|
|
|
|
|
firefly_cron_base_url="http://firefly/cron",
|
|
|
|
|
firefly_cron_token="token",
|
|
|
|
|
firefly_cron_timeout_sec=10.0,
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.firefly.PodExecutor", lambda *_args, **_kwargs: None)
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = FireflyService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_user("", "pw", wait=True)
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_user("alice@bstein.dev", "", wait=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_firefly_sync_missing_config(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
firefly_namespace="",
|
|
|
|
|
firefly_user_sync_cronjob="",
|
|
|
|
|
firefly_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
firefly_pod_label="app=firefly",
|
|
|
|
|
firefly_container="firefly",
|
|
|
|
|
firefly_cron_base_url="http://firefly/cron",
|
|
|
|
|
firefly_cron_token="token",
|
|
|
|
|
firefly_cron_timeout_sec=10.0,
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.settings", dummy)
|
|
|
|
|
svc = FireflyService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_user("alice@bstein.dev", "pw", wait=True)
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def test_firefly_run_cron(monkeypatch) -> None:
|
2026-01-20 18:11:02 -03:00
|
|
|
dummy = types.SimpleNamespace(
|
2026-01-20 23:03:04 -03:00
|
|
|
firefly_namespace="finance",
|
|
|
|
|
firefly_user_sync_cronjob="firefly-user-sync",
|
|
|
|
|
firefly_user_sync_wait_timeout_sec=60.0,
|
|
|
|
|
firefly_pod_label="app=firefly",
|
|
|
|
|
firefly_container="firefly",
|
|
|
|
|
firefly_cron_base_url="http://firefly/cron",
|
|
|
|
|
firefly_cron_token="token",
|
|
|
|
|
firefly_cron_timeout_sec=10.0,
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.firefly.settings", dummy)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
class DummyHTTP:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.calls = []
|
2026-01-20 18:11:02 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def __enter__(self):
|
|
|
|
|
return self
|
2026-01-20 18:11:02 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def __exit__(self, exc_type, exc, tb):
|
|
|
|
|
return False
|
2026-01-20 18:11:02 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def get(self, url):
|
|
|
|
|
self.calls.append(url)
|
|
|
|
|
return types.SimpleNamespace(status_code=200)
|
2026-01-20 18:11:02 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.firefly.httpx.Client", lambda *args, **kwargs: DummyHTTP())
|
2026-01-20 03:01:22 -03:00
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
svc = FireflyService()
|
|
|
|
|
result = svc.run_cron()
|
2026-01-20 03:01:22 -03:00
|
|
|
assert result["status"] == "ok"
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
|
2026-01-21 02:57:06 -03:00
|
|
|
def test_firefly_sync_users(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
firefly_namespace="finance",
|
|
|
|
|
firefly_user_sync_wait_timeout_sec=60.0,
|
|
|
|
|
firefly_pod_label="app=firefly",
|
|
|
|
|
firefly_container="firefly",
|
|
|
|
|
firefly_cron_base_url="http://firefly/cron",
|
|
|
|
|
firefly_cron_token="token",
|
|
|
|
|
firefly_cron_timeout_sec=10.0,
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.settings", dummy)
|
|
|
|
|
|
|
|
|
|
calls: list[tuple[str, str, str]] = []
|
|
|
|
|
|
|
|
|
|
class DummyAdmin:
|
|
|
|
|
def ready(self) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def iter_users(self, page_size=200, brief=False):
|
|
|
|
|
return [{"id": "1", "username": "alice", "attributes": {}}]
|
|
|
|
|
|
|
|
|
|
def get_user(self, user_id: str):
|
|
|
|
|
return {"id": user_id, "username": "alice", "attributes": {}}
|
|
|
|
|
|
|
|
|
|
def set_user_attribute(self, username: str, key: str, value: str) -> None:
|
|
|
|
|
calls.append((username, key, value))
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.keycloak_admin", DummyAdmin())
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.firefly.mailu.resolve_mailu_email",
|
|
|
|
|
lambda *_args, **_kwargs: "alice@bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.firefly.random_password", lambda *_args: "pw")
|
|
|
|
|
|
|
|
|
|
def fake_sync_user(self, *_args, **_kwargs):
|
|
|
|
|
return {"status": "ok", "detail": "ok"}
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(FireflyService, "sync_user", fake_sync_user)
|
|
|
|
|
|
|
|
|
|
svc = FireflyService()
|
|
|
|
|
result = svc.sync_users()
|
|
|
|
|
assert result["status"] == "ok"
|
|
|
|
|
assert any(key == "firefly_password" for _user, key, _value in calls)
|
|
|
|
|
assert any(key == "firefly_password_updated_at" for _user, key, _value in calls)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_mailu_sync_updates_attrs(monkeypatch) -> None:
|
2026-01-19 23:27:10 -03:00
|
|
|
dummy_settings = types.SimpleNamespace(
|
2026-01-21 02:57:06 -03:00
|
|
|
mailu_domain="bstein.dev",
|
2026-01-19 23:27:10 -03:00
|
|
|
mailu_db_host="localhost",
|
|
|
|
|
mailu_db_port=5432,
|
|
|
|
|
mailu_db_name="mailu",
|
|
|
|
|
mailu_db_user="mailu",
|
|
|
|
|
mailu_db_password="secret",
|
2026-01-21 02:57:06 -03:00
|
|
|
mailu_default_quota=20000000000,
|
|
|
|
|
mailu_system_users=[],
|
|
|
|
|
mailu_system_password="",
|
2026-01-19 23:27:10 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
2026-01-21 02:57:06 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.mailu.keycloak_admin.ready", lambda: True)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.mailu.keycloak_admin.iter_users",
|
|
|
|
|
lambda *args, **kwargs: [
|
|
|
|
|
{
|
|
|
|
|
"id": "1",
|
|
|
|
|
"username": "alice",
|
|
|
|
|
"enabled": True,
|
|
|
|
|
"email": "alice@example.com",
|
|
|
|
|
"attributes": {},
|
|
|
|
|
"firstName": "Alice",
|
|
|
|
|
"lastName": "Example",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
updates: list[tuple[str, dict[str, object]]] = []
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.mailu.keycloak_admin.update_user_safe",
|
|
|
|
|
lambda user_id, payload: updates.append((user_id, payload)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
mailbox_calls: list[tuple[str, str, str]] = []
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.mailu.MailuService._ensure_mailbox",
|
|
|
|
|
lambda self, _conn, email, password, display: mailbox_calls.append((email, password, display)) or True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class DummyConn:
|
|
|
|
|
def __enter__(self):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc, tb):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.psycopg.connect", lambda *args, **kwargs: DummyConn())
|
2026-01-19 23:27:10 -03:00
|
|
|
|
|
|
|
|
svc = MailuService()
|
2026-01-21 02:57:06 -03:00
|
|
|
summary = svc.sync("provision", force=True)
|
2026-01-19 23:27:10 -03:00
|
|
|
|
2026-01-21 02:57:06 -03:00
|
|
|
assert summary.processed == 1
|
|
|
|
|
assert summary.updated == 1
|
|
|
|
|
assert mailbox_calls
|
|
|
|
|
assert updates
|
|
|
|
|
assert "mailu_email" in updates[0][1]["attributes"]
|
2026-01-19 23:27:10 -03:00
|
|
|
|
|
|
|
|
|
2026-01-21 02:57:06 -03:00
|
|
|
def test_mailu_sync_skips_disabled(monkeypatch) -> None:
|
2026-01-20 18:11:02 -03:00
|
|
|
dummy_settings = types.SimpleNamespace(
|
2026-01-21 02:57:06 -03:00
|
|
|
mailu_domain="bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
mailu_db_host="localhost",
|
|
|
|
|
mailu_db_port=5432,
|
|
|
|
|
mailu_db_name="mailu",
|
|
|
|
|
mailu_db_user="mailu",
|
|
|
|
|
mailu_db_password="secret",
|
2026-01-21 02:57:06 -03:00
|
|
|
mailu_default_quota=20000000000,
|
|
|
|
|
mailu_system_users=[],
|
|
|
|
|
mailu_system_password="",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
2026-01-21 02:57:06 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.mailu.keycloak_admin.ready", lambda: True)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.mailu.keycloak_admin.iter_users",
|
|
|
|
|
lambda *args, **kwargs: [
|
|
|
|
|
{
|
|
|
|
|
"id": "1",
|
|
|
|
|
"username": "alice",
|
|
|
|
|
"enabled": True,
|
|
|
|
|
"attributes": {"mailu_enabled": ["false"]},
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
mailbox_calls: list[tuple[str, str, str]] = []
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.mailu.MailuService._ensure_mailbox",
|
|
|
|
|
lambda self, _conn, email, password, display: mailbox_calls.append((email, password, display)) or True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class DummyConn:
|
|
|
|
|
def __enter__(self):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc, tb):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.psycopg.connect", lambda *args, **kwargs: DummyConn())
|
|
|
|
|
|
2026-01-20 18:11:02 -03:00
|
|
|
svc = MailuService()
|
2026-01-21 02:57:06 -03:00
|
|
|
summary = svc.sync("provision")
|
|
|
|
|
|
|
|
|
|
assert summary.skipped == 1
|
|
|
|
|
assert mailbox_calls == []
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
|
2026-01-21 02:57:06 -03:00
|
|
|
def test_mailu_sync_system_mailboxes(monkeypatch) -> None:
|
2026-01-20 18:11:02 -03:00
|
|
|
dummy_settings = types.SimpleNamespace(
|
2026-01-21 02:57:06 -03:00
|
|
|
mailu_domain="bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
mailu_db_host="localhost",
|
|
|
|
|
mailu_db_port=5432,
|
|
|
|
|
mailu_db_name="mailu",
|
|
|
|
|
mailu_db_user="mailu",
|
|
|
|
|
mailu_db_password="secret",
|
2026-01-21 02:57:06 -03:00
|
|
|
mailu_default_quota=20000000000,
|
|
|
|
|
mailu_system_users=["no-reply-portal@bstein.dev"],
|
|
|
|
|
mailu_system_password="systempw",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
2026-01-21 02:57:06 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.mailu.keycloak_admin.ready", lambda: True)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.keycloak_admin.iter_users", lambda *args, **kwargs: [])
|
|
|
|
|
|
|
|
|
|
mailbox_calls: list[tuple[str, str, str]] = []
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.mailu.MailuService._ensure_mailbox",
|
|
|
|
|
lambda self, _conn, email, password, display: mailbox_calls.append((email, password, display)) or True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class DummyConn:
|
|
|
|
|
def __enter__(self):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc, tb):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.psycopg.connect", lambda *args, **kwargs: DummyConn())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = MailuService()
|
2026-01-21 02:57:06 -03:00
|
|
|
summary = svc.sync("schedule")
|
|
|
|
|
|
|
|
|
|
assert summary.system_mailboxes == 1
|
|
|
|
|
assert mailbox_calls[0][0] == "no-reply-portal@bstein.dev"
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
|
2026-01-20 00:06:50 -03:00
|
|
|
def test_vaultwarden_invite_uses_admin_session(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=600,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
|
|
|
|
)
|
|
|
|
|
client = DummyVaultwardenClient()
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.get_secret_value", lambda *args, **kwargs: "token")
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.httpx.Client", lambda *args, **kwargs: client)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.VaultwardenService._find_pod_ip",
|
|
|
|
|
staticmethod(lambda *args, **kwargs: "127.0.0.1"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
result = svc.invite_user("alice@bstein.dev")
|
|
|
|
|
|
|
|
|
|
assert result.ok is True
|
|
|
|
|
assert any(call[0] == "/admin" for call in client.calls)
|
|
|
|
|
assert any(call[0] == "/admin/invite" for call in client.calls)
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 18:11:02 -03:00
|
|
|
def test_vaultwarden_invite_handles_rate_limit(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=600,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
2026-01-19 19:01:32 -03:00
|
|
|
)
|
2026-01-20 18:11:02 -03:00
|
|
|
client = DummyVaultwardenClient()
|
|
|
|
|
client.responses["/admin/invite"] = DummyResponse(429, "rate limited")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.get_secret_value", lambda *args, **kwargs: "token")
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.httpx.Client", lambda *args, **kwargs: client)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.VaultwardenService._find_pod_ip",
|
|
|
|
|
staticmethod(lambda *args, **kwargs: "127.0.0.1"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
result = svc.invite_user("alice@bstein.dev")
|
|
|
|
|
assert result.status == "rate_limited"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_invite_existing_user(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=600,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
|
|
|
|
)
|
|
|
|
|
client = DummyVaultwardenClient()
|
|
|
|
|
client.responses["/admin/invite"] = DummyResponse(409, "user already exists")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.get_secret_value", lambda *args, **kwargs: "token")
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.httpx.Client", lambda *args, **kwargs: client)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.VaultwardenService._find_pod_ip",
|
|
|
|
|
staticmethod(lambda *args, **kwargs: "127.0.0.1"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
result = svc.invite_user("alice@bstein.dev")
|
|
|
|
|
assert result.status == "already_present"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_invite_rejects_invalid_email() -> None:
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
result = svc.invite_user("bad-email")
|
|
|
|
|
assert result.status == "invalid_email"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_invite_rate_limited_short_circuit() -> None:
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
svc._rate_limited_until = time.time() + 60
|
|
|
|
|
result = svc.invite_user("alice@bstein.dev")
|
|
|
|
|
assert result.status == "rate_limited"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_invite_handles_admin_exception(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=600,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.VaultwardenService._find_pod_ip",
|
|
|
|
|
staticmethod(lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("boom"))),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
svc,
|
|
|
|
|
"_admin_session",
|
|
|
|
|
lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("rate limited")),
|
|
|
|
|
)
|
|
|
|
|
result = svc.invite_user("alice@bstein.dev")
|
|
|
|
|
assert result.status == "rate_limited"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_invite_handles_bad_body(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=600,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class BadTextResponse:
|
|
|
|
|
def __init__(self, status_code=500):
|
|
|
|
|
self.status_code = status_code
|
|
|
|
|
|
|
|
|
|
def raise_for_status(self):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def text(self):
|
|
|
|
|
raise RuntimeError("boom")
|
|
|
|
|
|
|
|
|
|
class BadTextClient(DummyVaultwardenClient):
|
|
|
|
|
def post(self, path, json=None, data=None):
|
|
|
|
|
self.calls.append((path, json, data))
|
|
|
|
|
return BadTextResponse(500)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.get_secret_value", lambda *args, **kwargs: "token")
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.httpx.Client", lambda *args, **kwargs: BadTextClient())
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.VaultwardenService._find_pod_ip",
|
|
|
|
|
staticmethod(lambda *args, **kwargs: "127.0.0.1"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
result = svc.invite_user("alice@bstein.dev")
|
|
|
|
|
assert result.status == "error"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_invite_handles_fallback_skip(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=600,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.VaultwardenService._find_pod_ip",
|
|
|
|
|
staticmethod(lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("boom"))),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
monkeypatch.setattr(svc, "_admin_session", lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("nope")))
|
|
|
|
|
result = svc.invite_user("alice@bstein.dev")
|
|
|
|
|
assert result.status == "error"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_find_pod_ip(monkeypatch) -> None:
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.get_json",
|
|
|
|
|
lambda *args, **kwargs: {
|
|
|
|
|
"items": [
|
|
|
|
|
{
|
|
|
|
|
"status": {
|
|
|
|
|
"phase": "Running",
|
|
|
|
|
"podIP": "10.0.0.1",
|
|
|
|
|
"conditions": [{"type": "Ready", "status": "True"}],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert VaultwardenService._find_pod_ip("ns", "app=vaultwarden") == "10.0.0.1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_find_pod_ip_skips_missing_ip(monkeypatch) -> None:
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.get_json",
|
|
|
|
|
lambda *args, **kwargs: {
|
|
|
|
|
"items": [
|
|
|
|
|
{"status": {"phase": "Running", "podIP": ""}},
|
|
|
|
|
{"status": {"phase": "Running", "podIP": "10.0.0.2", "conditions": []}},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
assert VaultwardenService._find_pod_ip("ns", "app=vaultwarden") == "10.0.0.2"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_find_pod_ip_conditions_default_ready(monkeypatch) -> None:
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.get_json",
|
|
|
|
|
lambda *args, **kwargs: {
|
|
|
|
|
"items": [
|
|
|
|
|
{"status": {"phase": "Running", "podIP": "10.0.0.3", "conditions": ["bad"]}},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
assert VaultwardenService._find_pod_ip("ns", "app=vaultwarden") == "10.0.0.3"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_find_pod_ip_no_pods(monkeypatch) -> None:
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.get_json", lambda *args, **kwargs: {"items": []})
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
VaultwardenService._find_pod_ip("ns", "app=vaultwarden")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_find_pod_ip_missing_ip(monkeypatch) -> None:
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"ariadne.services.vaultwarden.get_json",
|
|
|
|
|
lambda *args, **kwargs: {
|
|
|
|
|
"items": [
|
|
|
|
|
{"status": {"phase": "Pending", "conditions": ["bad"]}},
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
VaultwardenService._find_pod_ip("ns", "app=vaultwarden")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_admin_session_rate_limit(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=1,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
|
|
|
|
)
|
|
|
|
|
client = DummyVaultwardenClient()
|
|
|
|
|
client.responses["/admin"] = DummyResponse(429, "")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.get_secret_value", lambda *args, **kwargs: "token")
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.httpx.Client", lambda *args, **kwargs: client)
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc._admin_session("http://vaultwarden")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_admin_session_reuses_client() -> None:
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
svc._admin_client = DummyVaultwardenClient()
|
|
|
|
|
svc._admin_session_expires_at = time.time() + 60
|
|
|
|
|
svc._admin_session_base_url = "http://vaultwarden"
|
|
|
|
|
|
|
|
|
|
client = svc._admin_session("http://vaultwarden")
|
|
|
|
|
assert client is svc._admin_client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_admin_session_rate_limited_until() -> None:
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
svc._rate_limited_until = time.time() + 60
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc._admin_session("http://vaultwarden")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_vaultwarden_admin_session_closes_existing(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
vaultwarden_namespace="vaultwarden",
|
|
|
|
|
vaultwarden_admin_secret_name="vaultwarden-admin",
|
|
|
|
|
vaultwarden_admin_secret_key="ADMIN_TOKEN",
|
|
|
|
|
vaultwarden_admin_rate_limit_backoff_sec=600,
|
|
|
|
|
vaultwarden_admin_session_ttl_sec=900,
|
|
|
|
|
vaultwarden_service_host="vaultwarden-service.vaultwarden.svc.cluster.local",
|
|
|
|
|
vaultwarden_pod_label="app=vaultwarden",
|
|
|
|
|
vaultwarden_pod_port=80,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class CloseFail:
|
|
|
|
|
def close(self):
|
|
|
|
|
raise RuntimeError("boom")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.get_secret_value", lambda *args, **kwargs: "token")
|
|
|
|
|
monkeypatch.setattr("ariadne.services.vaultwarden.httpx.Client", lambda *args, **kwargs: DummyVaultwardenClient())
|
|
|
|
|
|
|
|
|
|
svc = VaultwardenService()
|
|
|
|
|
svc._admin_client = CloseFail()
|
|
|
|
|
svc._admin_session_expires_at = time.time() - 10
|
|
|
|
|
svc._admin_session_base_url = "http://old"
|
|
|
|
|
|
|
|
|
|
assert svc._admin_session("http://vaultwarden") is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_nextcloud_missing_config(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
nextcloud_namespace="",
|
|
|
|
|
nextcloud_mail_sync_cronjob="",
|
|
|
|
|
nextcloud_mail_sync_wait_timeout_sec=90.0,
|
|
|
|
|
nextcloud_mail_sync_job_ttl_sec=3600,
|
2026-01-20 23:03:04 -03:00
|
|
|
nextcloud_pod_label="app=nextcloud",
|
|
|
|
|
nextcloud_container="nextcloud",
|
|
|
|
|
nextcloud_exec_timeout_sec=30.0,
|
|
|
|
|
nextcloud_db_host="",
|
|
|
|
|
nextcloud_db_port=5432,
|
|
|
|
|
nextcloud_db_name="nextcloud",
|
|
|
|
|
nextcloud_db_user="nextcloud",
|
|
|
|
|
nextcloud_db_password="",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
mailu_host="mail.bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.settings", dummy)
|
|
|
|
|
svc = NextcloudService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_mail("alice")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wger_sync_missing_inputs(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="health",
|
|
|
|
|
wger_user_sync_cronjob="wger-user-sync",
|
|
|
|
|
wger_admin_cronjob="wger-admin-ensure",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="admin",
|
|
|
|
|
wger_admin_password="pw",
|
|
|
|
|
wger_admin_email="admin@bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.wger.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = WgerService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_user("", "email", "pw", wait=True)
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_user("alice", "email", "", wait=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wger_sync_missing_config(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="",
|
|
|
|
|
wger_user_sync_cronjob="",
|
|
|
|
|
wger_admin_cronjob="wger-admin-ensure",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="admin",
|
|
|
|
|
wger_admin_password="pw",
|
|
|
|
|
wger_admin_email="admin@bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.wger.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
svc = WgerService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_user("alice", "email", "pw", wait=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wger_ensure_admin(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="health",
|
|
|
|
|
wger_user_sync_cronjob="wger-user-sync",
|
|
|
|
|
wger_admin_cronjob="wger-admin-ensure",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="admin",
|
|
|
|
|
wger_admin_password="pw",
|
|
|
|
|
wger_admin_email="admin@bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.wger.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = WgerService()
|
|
|
|
|
result = svc.ensure_admin(wait=True)
|
|
|
|
|
assert result["status"] == "ok"
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def test_wger_ensure_admin_missing_creds(monkeypatch) -> None:
|
2026-01-20 18:11:02 -03:00
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="health",
|
|
|
|
|
wger_user_sync_cronjob="wger-user-sync",
|
|
|
|
|
wger_admin_cronjob="wger-admin-ensure",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="",
|
|
|
|
|
wger_admin_password="",
|
|
|
|
|
wger_admin_email="",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.wger.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = WgerService()
|
2026-01-20 23:03:04 -03:00
|
|
|
result = svc.ensure_admin(wait=True)
|
|
|
|
|
assert result["status"] == "error"
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wger_ensure_admin_missing_config(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
wger_namespace="",
|
|
|
|
|
wger_user_sync_cronjob="wger-user-sync",
|
|
|
|
|
wger_admin_cronjob="",
|
|
|
|
|
wger_user_sync_wait_timeout_sec=60.0,
|
2026-01-20 23:03:04 -03:00
|
|
|
wger_pod_label="app=wger",
|
|
|
|
|
wger_container="wger",
|
|
|
|
|
wger_admin_username="admin",
|
|
|
|
|
wger_admin_password="pw",
|
|
|
|
|
wger_admin_email="admin@bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.wger.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.wger.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
svc = WgerService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.ensure_admin(wait=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_mailu_mailbox_exists_handles_error(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
mailu_sync_url="",
|
|
|
|
|
mailu_sync_wait_timeout_sec=10.0,
|
|
|
|
|
mailu_db_host="localhost",
|
|
|
|
|
mailu_db_port=5432,
|
|
|
|
|
mailu_db_name="mailu",
|
|
|
|
|
mailu_db_user="mailu",
|
|
|
|
|
mailu_db_password="secret",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.psycopg.connect", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("boom")))
|
|
|
|
|
|
|
|
|
|
svc = MailuService()
|
|
|
|
|
assert svc.mailbox_exists("alice@bstein.dev") is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_mailu_mailbox_exists_success(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
mailu_sync_url="",
|
|
|
|
|
mailu_sync_wait_timeout_sec=10.0,
|
|
|
|
|
mailu_db_host="localhost",
|
|
|
|
|
mailu_db_port=5432,
|
|
|
|
|
mailu_db_name="mailu",
|
|
|
|
|
mailu_db_user="mailu",
|
|
|
|
|
mailu_db_password="secret",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
|
|
|
|
|
|
|
|
|
class DummyCursor:
|
|
|
|
|
def execute(self, *_args, **_kwargs):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def fetchone(self):
|
|
|
|
|
return {"id": 1}
|
|
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __exit__(self, *_args):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
class DummyConn:
|
|
|
|
|
def cursor(self):
|
|
|
|
|
return DummyCursor()
|
|
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __exit__(self, *_args):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.psycopg.connect", lambda *args, **kwargs: DummyConn())
|
|
|
|
|
|
|
|
|
|
svc = MailuService()
|
|
|
|
|
assert svc.mailbox_exists("alice@bstein.dev") is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_mailu_wait_for_mailbox(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
mailu_sync_url="",
|
|
|
|
|
mailu_sync_wait_timeout_sec=10.0,
|
|
|
|
|
mailu_db_host="localhost",
|
|
|
|
|
mailu_db_port=5432,
|
|
|
|
|
mailu_db_name="mailu",
|
|
|
|
|
mailu_db_user="mailu",
|
|
|
|
|
mailu_db_password="secret",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
|
|
|
|
monkeypatch.setattr(MailuService, "mailbox_exists", lambda self, email: True)
|
|
|
|
|
|
|
|
|
|
svc = MailuService()
|
|
|
|
|
assert svc.wait_for_mailbox("alice@bstein.dev", timeout_sec=1.0) is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_mailu_mailbox_exists_empty_email(monkeypatch) -> None:
|
|
|
|
|
dummy_settings = types.SimpleNamespace(
|
|
|
|
|
mailu_sync_url="",
|
|
|
|
|
mailu_sync_wait_timeout_sec=10.0,
|
|
|
|
|
mailu_db_host="localhost",
|
|
|
|
|
mailu_db_port=5432,
|
|
|
|
|
mailu_db_name="mailu",
|
|
|
|
|
mailu_db_user="mailu",
|
|
|
|
|
mailu_db_password="secret",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.mailu.settings", dummy_settings)
|
|
|
|
|
svc = MailuService()
|
|
|
|
|
assert svc.mailbox_exists("") is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_nextcloud_sync_missing_username(monkeypatch) -> None:
|
|
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
nextcloud_namespace="nextcloud",
|
|
|
|
|
nextcloud_mail_sync_cronjob="nextcloud-mail-sync",
|
|
|
|
|
nextcloud_mail_sync_wait_timeout_sec=90.0,
|
|
|
|
|
nextcloud_mail_sync_job_ttl_sec=3600,
|
2026-01-20 23:03:04 -03:00
|
|
|
nextcloud_pod_label="app=nextcloud",
|
|
|
|
|
nextcloud_container="nextcloud",
|
|
|
|
|
nextcloud_exec_timeout_sec=30.0,
|
|
|
|
|
nextcloud_db_host="",
|
|
|
|
|
nextcloud_db_port=5432,
|
|
|
|
|
nextcloud_db_name="nextcloud",
|
|
|
|
|
nextcloud_db_user="nextcloud",
|
|
|
|
|
nextcloud_db_password="",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
mailu_host="mail.bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = NextcloudService()
|
|
|
|
|
with pytest.raises(RuntimeError):
|
|
|
|
|
svc.sync_mail(" ", wait=True)
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 23:03:04 -03:00
|
|
|
def test_nextcloud_sync_no_match(monkeypatch) -> None:
|
2026-01-20 18:11:02 -03:00
|
|
|
dummy = types.SimpleNamespace(
|
|
|
|
|
nextcloud_namespace="nextcloud",
|
|
|
|
|
nextcloud_mail_sync_cronjob="nextcloud-mail-sync",
|
|
|
|
|
nextcloud_mail_sync_wait_timeout_sec=90.0,
|
|
|
|
|
nextcloud_mail_sync_job_ttl_sec=3600,
|
2026-01-20 23:03:04 -03:00
|
|
|
nextcloud_pod_label="app=nextcloud",
|
|
|
|
|
nextcloud_container="nextcloud",
|
|
|
|
|
nextcloud_exec_timeout_sec=30.0,
|
|
|
|
|
nextcloud_db_host="",
|
|
|
|
|
nextcloud_db_port=5432,
|
|
|
|
|
nextcloud_db_name="nextcloud",
|
|
|
|
|
nextcloud_db_user="nextcloud",
|
|
|
|
|
nextcloud_db_password="",
|
|
|
|
|
mailu_domain="bstein.dev",
|
|
|
|
|
mailu_host="mail.bstein.dev",
|
2026-01-20 18:11:02 -03:00
|
|
|
)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.settings", dummy)
|
2026-01-20 23:03:04 -03:00
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.keycloak_admin.ready", lambda: True)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.keycloak_admin.find_user", lambda *_args, **_kwargs: None)
|
|
|
|
|
monkeypatch.setattr("ariadne.services.nextcloud.PodExecutor", lambda *_args, **_kwargs: DummyExecutor())
|
2026-01-20 18:11:02 -03:00
|
|
|
|
|
|
|
|
svc = NextcloudService()
|
|
|
|
|
result = svc.sync_mail("alice", wait=False)
|
2026-01-20 23:03:04 -03:00
|
|
|
assert result["status"] == "ok"
|