test(ariadne): cover nextcloud sync edge paths
This commit is contained in:
parent
6b6b9677be
commit
b73e678bfc
@ -34,13 +34,7 @@ class NextcloudService:
|
|||||||
settings.nextcloud_container,
|
settings.nextcloud_container,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _exec_with_fallback(
|
def _exec_with_fallback(self, primary: list[str], fallback: list[str], env: dict[str, str] | None = None, check: bool = True) -> ExecResult:
|
||||||
self,
|
|
||||||
primary: list[str],
|
|
||||||
fallback: list[str],
|
|
||||||
env: dict[str, str] | None = None,
|
|
||||||
check: bool = True,
|
|
||||||
) -> ExecResult:
|
|
||||||
try:
|
try:
|
||||||
result = self._executor.exec(
|
result = self._executor.exec(
|
||||||
primary,
|
primary,
|
||||||
@ -67,12 +61,7 @@ class NextcloudService:
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _occ_exec(
|
def _occ_exec(self, args: list[str], env: dict[str, str] | None = None, check: bool = True) -> ExecResult:
|
||||||
self,
|
|
||||||
args: list[str],
|
|
||||||
env: dict[str, str] | None = None,
|
|
||||||
check: bool = True,
|
|
||||||
) -> ExecResult:
|
|
||||||
command = ["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", *args]
|
command = ["runuser", "-u", "www-data", "--", "php", "/var/www/html/occ", *args]
|
||||||
fallback = ["php", "/var/www/html/occ", *args]
|
fallback = ["php", "/var/www/html/occ", *args]
|
||||||
return self._exec_with_fallback(command, fallback, env=env, check=check)
|
return self._exec_with_fallback(command, fallback, env=env, check=check)
|
||||||
@ -81,12 +70,7 @@ class NextcloudService:
|
|||||||
result = self._occ_exec(args, check=True)
|
result = self._occ_exec(args, check=True)
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
def _ensure_nextcloud_user(
|
def _ensure_nextcloud_user(self, username: str, mailu_email: str, display_name: str) -> None:
|
||||||
self,
|
|
||||||
username: str,
|
|
||||||
mailu_email: str,
|
|
||||||
display_name: str,
|
|
||||||
) -> None:
|
|
||||||
result = self._occ_exec(["user:info", username], check=False)
|
result = self._occ_exec(["user:info", username], check=False)
|
||||||
if result.ok:
|
if result.ok:
|
||||||
return
|
return
|
||||||
@ -191,11 +175,7 @@ class NextcloudService:
|
|||||||
full_user = user
|
full_user = user
|
||||||
return username_val, user_id, full_user
|
return username_val, user_id, full_user
|
||||||
|
|
||||||
def _list_mail_accounts_safe(
|
def _list_mail_accounts_safe(self, username: str, counters: MailSyncCounters) -> list[tuple[str, str]] | None:
|
||||||
self,
|
|
||||||
username: str,
|
|
||||||
counters: MailSyncCounters,
|
|
||||||
) -> list[tuple[str, str]] | None:
|
|
||||||
try:
|
try:
|
||||||
return self._list_mail_accounts(username)
|
return self._list_mail_accounts(username)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@ -207,11 +187,7 @@ class NextcloudService:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _select_primary_account(
|
def _select_primary_account(self, mailu_accounts: list[tuple[str, str]], mailu_email: str) -> tuple[str, str]:
|
||||||
self,
|
|
||||||
mailu_accounts: list[tuple[str, str]],
|
|
||||||
mailu_email: str,
|
|
||||||
) -> tuple[str, str]:
|
|
||||||
primary_id = ""
|
primary_id = ""
|
||||||
primary_email = ""
|
primary_email = ""
|
||||||
for account_id, account_email in mailu_accounts:
|
for account_id, account_email in mailu_accounts:
|
||||||
@ -224,13 +200,7 @@ class NextcloudService:
|
|||||||
break
|
break
|
||||||
return primary_id, primary_email
|
return primary_id, primary_email
|
||||||
|
|
||||||
def _update_mail_account(
|
def _update_mail_account(self, username: str, primary_id: str, mailu_email: str, app_pw: str) -> str | None:
|
||||||
self,
|
|
||||||
username: str,
|
|
||||||
primary_id: str,
|
|
||||||
mailu_email: str,
|
|
||||||
app_pw: str,
|
|
||||||
) -> str | None:
|
|
||||||
try:
|
try:
|
||||||
self._occ(
|
self._occ(
|
||||||
[
|
[
|
||||||
@ -295,12 +265,7 @@ class NextcloudService:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return str(exc)
|
return str(exc)
|
||||||
|
|
||||||
def _delete_extra_accounts(
|
def _delete_extra_accounts(self, mailu_accounts: list[tuple[str, str]], primary_id: str, counters: MailSyncCounters) -> int:
|
||||||
self,
|
|
||||||
mailu_accounts: list[tuple[str, str]],
|
|
||||||
primary_id: str,
|
|
||||||
counters: MailSyncCounters,
|
|
||||||
) -> int:
|
|
||||||
deleted = 0
|
deleted = 0
|
||||||
for account_id, _account_email in mailu_accounts:
|
for account_id, _account_email in mailu_accounts:
|
||||||
if account_id == primary_id:
|
if account_id == primary_id:
|
||||||
@ -319,11 +284,7 @@ class NextcloudService:
|
|||||||
if email.lower().endswith(f"@{settings.mailu_domain.lower()}")
|
if email.lower().endswith(f"@{settings.mailu_domain.lower()}")
|
||||||
]
|
]
|
||||||
|
|
||||||
def _summarize_mail_accounts(
|
def _summarize_mail_accounts(self, accounts: list[tuple[str, str]], mailu_email: str) -> tuple[int, str, list[str]]:
|
||||||
self,
|
|
||||||
accounts: list[tuple[str, str]],
|
|
||||||
mailu_email: str,
|
|
||||||
) -> tuple[int, str, list[str]]:
|
|
||||||
mailu_accounts = self._mailu_accounts(accounts)
|
mailu_accounts = self._mailu_accounts(accounts)
|
||||||
account_count = len(mailu_accounts)
|
account_count = len(mailu_accounts)
|
||||||
primary_email = ""
|
primary_email = ""
|
||||||
@ -337,11 +298,7 @@ class NextcloudService:
|
|||||||
primary_email = account_email
|
primary_email = account_email
|
||||||
return account_count, primary_email, editor_mode_ids
|
return account_count, primary_email, editor_mode_ids
|
||||||
|
|
||||||
def _mail_sync_context(
|
def _mail_sync_context(self, user: dict[str, Any], counters: MailSyncCounters) -> tuple[str, str, str, str, dict[str, Any]] | None:
|
||||||
self,
|
|
||||||
user: dict[str, Any],
|
|
||||||
counters: MailSyncCounters,
|
|
||||||
) -> tuple[str, str, str, str, dict[str, Any]] | None:
|
|
||||||
normalized = self._normalize_user(user)
|
normalized = self._normalize_user(user)
|
||||||
if not normalized:
|
if not normalized:
|
||||||
counters.skipped += 1
|
counters.skipped += 1
|
||||||
@ -360,14 +317,7 @@ class NextcloudService:
|
|||||||
pass
|
pass
|
||||||
return username, user_id, mailu_email, app_pw, full_user
|
return username, user_id, mailu_email, app_pw, full_user
|
||||||
|
|
||||||
def _sync_mail_accounts(
|
def _sync_mail_accounts(self, username: str, mailu_email: str, app_pw: str, accounts: list[tuple[str, str]], counters: MailSyncCounters) -> bool:
|
||||||
self,
|
|
||||||
username: str,
|
|
||||||
mailu_email: str,
|
|
||||||
app_pw: str,
|
|
||||||
accounts: list[tuple[str, str]],
|
|
||||||
counters: MailSyncCounters,
|
|
||||||
) -> bool:
|
|
||||||
mailu_accounts = self._mailu_accounts(accounts)
|
mailu_accounts = self._mailu_accounts(accounts)
|
||||||
if mailu_accounts:
|
if mailu_accounts:
|
||||||
primary_id, _primary_email = self._select_primary_account(mailu_accounts, mailu_email)
|
primary_id, _primary_email = self._select_primary_account(mailu_accounts, mailu_email)
|
||||||
@ -385,12 +335,7 @@ class NextcloudService:
|
|||||||
counters.created += 1
|
counters.created += 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _apply_mail_metadata(
|
def _apply_mail_metadata(self, user_id: str, mailu_email: str, accounts: list[tuple[str, str]]) -> None:
|
||||||
self,
|
|
||||||
user_id: str,
|
|
||||||
mailu_email: str,
|
|
||||||
accounts: list[tuple[str, str]],
|
|
||||||
) -> None:
|
|
||||||
account_count, primary_email, editor_mode_ids = self._summarize_mail_accounts(accounts, mailu_email)
|
account_count, primary_email, editor_mode_ids = self._summarize_mail_accounts(accounts, mailu_email)
|
||||||
self._set_editor_mode_richtext(editor_mode_ids)
|
self._set_editor_mode_richtext(editor_mode_ids)
|
||||||
if user_id:
|
if user_id:
|
||||||
|
|||||||
@ -186,3 +186,118 @@ def test_nextcloud_sync_user_mail_and_external_api(monkeypatch) -> None:
|
|||||||
monkeypatch.setattr(nextcloud_module, "settings", _settings(nextcloud_url=""))
|
monkeypatch.setattr(nextcloud_module, "settings", _settings(nextcloud_url=""))
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
svc._external_api("GET", "/apps")
|
svc._external_api("GET", "/apps")
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_cron_and_occ_account_listing_edges(monkeypatch) -> None:
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings(nextcloud_namespace=""))
|
||||||
|
svc = NextcloudService()
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
svc.run_cron()
|
||||||
|
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings())
|
||||||
|
svc = NextcloudService()
|
||||||
|
monkeypatch.setattr(svc, "_exec_with_fallback", lambda *_args, **_kwargs: (_ for _ in ()).throw(ExecError("boom")))
|
||||||
|
assert svc.run_cron() == {"status": "error", "detail": "boom"}
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_exec_with_fallback", lambda *_args, **_kwargs: types.SimpleNamespace(stdout="", stderr="", ok=True))
|
||||||
|
assert svc.run_cron() == {"status": "ok"}
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_occ", lambda args: "raw export" if args == ["mail:account:export", "alice"] else "")
|
||||||
|
monkeypatch.setattr(nextcloud_module, "_parse_mail_export", lambda output: [("1", output)])
|
||||||
|
assert svc._list_mail_accounts("alice") == [("1", "raw export")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_editor_mode_skip_and_failure_edges(monkeypatch) -> None:
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings())
|
||||||
|
svc = NextcloudService()
|
||||||
|
svc._set_editor_mode_richtext(["not-a-number"])
|
||||||
|
svc._set_editor_mode_richtext(["12"])
|
||||||
|
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings(nextcloud_db_host="db", nextcloud_db_password="pw"))
|
||||||
|
monkeypatch.setattr(nextcloud_module.psycopg, "connect", lambda **_kwargs: (_ for _ in ()).throw(RuntimeError("db down")))
|
||||||
|
svc._set_editor_mode_richtext(["12"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_keycloak_metadata_and_normalization_failures(monkeypatch) -> None:
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings())
|
||||||
|
svc = NextcloudService()
|
||||||
|
monkeypatch.setattr(nextcloud_module.keycloak_admin, "update_user_safe", lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("kc down")))
|
||||||
|
svc._set_user_mail_meta("uid", "alice@bstein.dev", 1)
|
||||||
|
|
||||||
|
assert svc._normalize_user({"username": "alice", "enabled": False}) is None
|
||||||
|
user = {"id": "uid", "username": "alice", "enabled": True}
|
||||||
|
monkeypatch.setattr(nextcloud_module.keycloak_admin, "get_user", lambda _user_id: (_ for _ in ()).throw(RuntimeError("kc down")))
|
||||||
|
assert svc._normalize_user(user) == ("alice", "uid", user)
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_mail_sync_context_skip_and_attribute_edges(monkeypatch) -> None:
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings())
|
||||||
|
svc = NextcloudService()
|
||||||
|
counters = MailSyncCounters()
|
||||||
|
|
||||||
|
assert svc._mail_sync_context({"username": ""}, counters) is None
|
||||||
|
assert svc._mail_sync_context({"username": "alice", "attributes": {}}, counters) is None
|
||||||
|
assert counters.skipped == 2
|
||||||
|
|
||||||
|
user = {"username": "alice", "attributes": {"mailu_app_password": ["pw"]}}
|
||||||
|
monkeypatch.setattr(nextcloud_module, "_resolve_mailu_email", lambda username, _user: f"{username}@bstein.dev")
|
||||||
|
monkeypatch.setattr(nextcloud_module.keycloak_admin, "set_user_attribute", lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("kc down")))
|
||||||
|
assert svc._mail_sync_context(user, counters) == ("alice", "", "alice@bstein.dev", "pw", user)
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_sync_user_mail_short_circuits(monkeypatch) -> None:
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings())
|
||||||
|
svc = NextcloudService()
|
||||||
|
counters = MailSyncCounters()
|
||||||
|
svc._sync_user_mail({"username": ""}, counters)
|
||||||
|
assert counters.skipped == 1
|
||||||
|
|
||||||
|
context = ("alice", "uid", "alice@bstein.dev", "pw", {"username": "alice"})
|
||||||
|
monkeypatch.setattr(svc, "_mail_sync_context", lambda *_args: context)
|
||||||
|
monkeypatch.setattr(svc, "_ensure_nextcloud_user", lambda *_args: (_ for _ in ()).throw(RuntimeError("ensure failed")))
|
||||||
|
svc._sync_user_mail({"username": "alice"}, counters)
|
||||||
|
assert counters.last_error == "nextcloud user ensure failed: ensure failed"
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_ensure_nextcloud_user", lambda *_args: None)
|
||||||
|
monkeypatch.setattr(svc, "_list_mail_accounts_safe", lambda *_args: None)
|
||||||
|
before = counters.processed
|
||||||
|
svc._sync_user_mail({"username": "alice"}, counters)
|
||||||
|
assert counters.processed == before
|
||||||
|
|
||||||
|
monkeypatch.setattr(svc, "_list_mail_accounts_safe", lambda *_args: [("1", "alice@bstein.dev")])
|
||||||
|
monkeypatch.setattr(svc, "_sync_mail_accounts", lambda *_args: False)
|
||||||
|
svc._sync_user_mail({"username": "alice"}, counters)
|
||||||
|
assert counters.processed == before + 1
|
||||||
|
|
||||||
|
account_calls = iter([[("1", "alice@bstein.dev")], None])
|
||||||
|
monkeypatch.setattr(svc, "_list_mail_accounts_safe", lambda *_args: next(account_calls))
|
||||||
|
monkeypatch.setattr(svc, "_sync_mail_accounts", lambda *_args: True)
|
||||||
|
applied: list[tuple[str, str, list[tuple[str, str]]]] = []
|
||||||
|
monkeypatch.setattr(svc, "_apply_mail_metadata", lambda *args: applied.append(args))
|
||||||
|
svc._sync_user_mail({"username": "alice"}, counters)
|
||||||
|
assert not applied
|
||||||
|
|
||||||
|
|
||||||
|
def test_nextcloud_external_api_credentials_and_non_json_edges(monkeypatch) -> None:
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings(nextcloud_admin_password=""))
|
||||||
|
svc = NextcloudService()
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
svc._external_api("GET", "/apps")
|
||||||
|
|
||||||
|
class FakeClient:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc, tb) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def request(self, *_args, **_kwargs):
|
||||||
|
return DummyResponse()
|
||||||
|
|
||||||
|
monkeypatch.setattr(nextcloud_module, "settings", _settings())
|
||||||
|
monkeypatch.setattr(nextcloud_module.httpx, "Client", FakeClient)
|
||||||
|
assert svc._external_api("GET", "/apps") == {}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user