ariadne/tests/test_app.py

927 lines
33 KiB
Python
Raw Normal View History

from __future__ import annotations
import dataclasses
from datetime import datetime, timezone
import os
from fastapi import HTTPException
from fastapi.testclient import TestClient
os.environ.setdefault("PORTAL_DATABASE_URL", "postgresql://user:pass@localhost/db")
from ariadne.auth.keycloak import AuthContext
import ariadne.app as app_module
def _client(monkeypatch, ctx: AuthContext) -> TestClient:
monkeypatch.setattr(app_module.authenticator, "authenticate", lambda token: ctx)
monkeypatch.setattr(app_module.db, "ensure_schema", lambda: None)
monkeypatch.setattr(app_module.provisioning, "start", lambda: None)
monkeypatch.setattr(app_module.scheduler, "start", lambda: None)
monkeypatch.setattr(app_module.provisioning, "stop", lambda: None)
monkeypatch.setattr(app_module.scheduler, "stop", lambda: None)
monkeypatch.setattr(app_module.db, "close", lambda: None)
monkeypatch.setattr(app_module.storage, "record_event", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.storage, "record_task_run", lambda *args, **kwargs: None)
return TestClient(app_module.app)
def test_health_ok(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=[], claims={})
client = _client(monkeypatch, ctx)
resp = client.get("/health")
assert resp.status_code == 200
assert resp.json() == {"ok": True}
def test_startup_and_shutdown(monkeypatch) -> None:
monkeypatch.setattr(app_module.db, "ensure_schema", lambda: None)
monkeypatch.setattr(app_module.provisioning, "start", lambda: None)
monkeypatch.setattr(app_module.scheduler, "add_task", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.scheduler, "start", lambda: None)
monkeypatch.setattr(app_module.scheduler, "stop", lambda: None)
monkeypatch.setattr(app_module.provisioning, "stop", lambda: None)
monkeypatch.setattr(app_module.db, "close", lambda: None)
app_module._startup()
app_module._shutdown()
def test_record_event_handles_exception(monkeypatch) -> None:
monkeypatch.setattr(app_module.storage, "record_event", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
app_module._record_event("event", {"ok": True})
def test_parse_event_detail_variants() -> None:
assert app_module._parse_event_detail(None) == ""
assert app_module._parse_event_detail("not-json") == "not-json"
def test_missing_auth_header(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=[], claims={})
client = _client(monkeypatch, ctx)
resp = client.get("/api/admin/access/requests")
assert resp.status_code == 401
def test_invalid_token(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=[], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.authenticator, "authenticate", lambda token: (_ for _ in ()).throw(ValueError("bad")))
resp = client.get(
"/api/admin/access/requests",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 401
def test_forbidden_admin(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
resp = client.get(
"/api/admin/access/requests",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 403
def test_account_access_denied(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=[], claims={})
client = _client(monkeypatch, ctx)
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 403
def test_metrics_endpoint(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=[], claims={})
client = _client(monkeypatch, ctx)
resp = client.get("/metrics")
assert resp.status_code == 200
def test_mailu_event_endpoint(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=[], claims={})
client = _client(monkeypatch, ctx)
class DummyEvents:
def handle_event(self, payload):
assert payload == {"wait": False}
return 202, {"status": "accepted"}
monkeypatch.setattr(app_module, "mailu_events", DummyEvents())
resp = client.post("/events", json={"wait": False})
assert resp.status_code == 202
assert resp.json()["status"] == "accepted"
def test_list_access_requests(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
now = datetime.now(timezone.utc)
monkeypatch.setattr(
app_module.storage,
"list_pending_requests",
lambda: [
{
"request_code": "REQ1",
"username": "alice",
"contact_email": "alice@example.com",
"note": "hello",
"status": "pending",
"created_at": now,
}
],
)
resp = client.get(
"/api/admin/access/requests",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["requests"][0]["username"] == "alice"
def test_list_access_requests_error(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.storage, "list_pending_requests", lambda: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.get(
"/api/admin/access/requests",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 502
def test_list_audit_events(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
now = datetime.now(timezone.utc)
monkeypatch.setattr(
app_module.storage,
"list_events",
lambda **kwargs: [
{
"id": 1,
"event_type": "mailu_rotate",
"detail": '{"status":"ok"}',
"created_at": now,
}
],
)
resp = client.get(
"/api/admin/audit/events",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["events"][0]["detail"]["status"] == "ok"
def test_list_audit_events_error(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.storage, "list_events", lambda **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.get(
"/api/admin/audit/events",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 502
def test_list_audit_task_runs(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
now = datetime.now(timezone.utc)
monkeypatch.setattr(
app_module.storage,
"list_task_runs",
lambda **kwargs: [
{
"id": 1,
"request_code": "REQ1",
"task": "mailu_sync",
"status": "ok",
"detail": "done",
"started_at": now,
"finished_at": now,
"duration_ms": 120,
}
],
)
resp = client.get(
"/api/admin/audit/task-runs",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["task_runs"][0]["task"] == "mailu_sync"
def test_list_audit_task_runs_error(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.storage, "list_task_runs", lambda **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.get(
"/api/admin/audit/task-runs",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 502
def test_access_flags_from_keycloak(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "list_group_names", lambda **kwargs: ["demo", "test"])
resp = client.get(
"/api/admin/access/flags",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
assert resp.json()["flags"] == ["demo", "test"]
def test_access_flags_fallback(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: False)
monkeypatch.setattr(
app_module,
"settings",
dataclasses.replace(app_module.settings, allowed_flag_groups=["demo"]),
)
resp = client.get(
"/api/admin/access/flags",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
assert resp.json()["flags"] == ["demo"]
def test_access_request_approve(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
captured = {}
def fake_fetchone(_query, params):
captured["flags"] = params[1]
return {"request_code": "REQ1"}
monkeypatch.setattr(app_module.db, "fetchone", fake_fetchone)
monkeypatch.setattr(app_module.provisioning, "provision_access_request", lambda code: None)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "list_group_names", lambda **kwargs: ["demo"])
resp = client.post(
"/api/admin/access/requests/alice/approve",
headers={"Authorization": "Bearer token"},
json={"flags": ["demo", "test", "admin"], "note": "ok"},
)
assert resp.status_code == 200
assert resp.json()["request_code"] == "REQ1"
assert captured["flags"] == ["demo"]
def test_access_request_approve_bad_json(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.db, "fetchone", lambda *args, **kwargs: {"request_code": "REQ1"})
resp = client.post(
"/api/admin/access/requests/alice/approve",
headers={"Authorization": "Bearer token", "Content-Type": "application/json"},
data="{bad}",
)
assert resp.status_code == 200
def test_access_request_approve_db_error(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.db, "fetchone", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.post(
"/api/admin/access/requests/alice/approve",
headers={"Authorization": "Bearer token"},
json={},
)
assert resp.status_code == 502
def test_access_request_approve_skipped(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.db, "fetchone", lambda *args, **kwargs: None)
resp = client.post(
"/api/admin/access/requests/alice/approve",
headers={"Authorization": "Bearer token"},
json={"flags": ["demo"]},
)
assert resp.status_code == 200
assert resp.json()["request_code"] == ""
def test_access_request_deny(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.db, "fetchone", lambda *args, **kwargs: {"request_code": "REQ2"})
resp = client.post(
"/api/admin/access/requests/alice/deny",
headers={"Authorization": "Bearer token"},
json={"note": "no"},
)
assert resp.status_code == 200
assert resp.json()["request_code"] == "REQ2"
def test_access_request_deny_db_error(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.db, "fetchone", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.post(
"/api/admin/access/requests/alice/deny",
headers={"Authorization": "Bearer token"},
json={},
)
assert resp.status_code == 502
def test_access_request_deny_skipped(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.db, "fetchone", lambda *args, **kwargs: None)
resp = client.post(
"/api/admin/access/requests/alice/deny",
headers={"Authorization": "Bearer token"},
json={"note": "no"},
)
assert resp.status_code == 200
assert resp.json()["request_code"] == ""
def test_rotate_mailu_password(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.mailu, "ready", lambda: True)
monkeypatch.setattr(app_module.mailu, "sync", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.nextcloud, "sync_mail", lambda *args, **kwargs: {"status": "ok"})
resp = client.post(
"/api/account/mailu/rotate",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["sync_ok"] is True
assert payload["password"]
def test_rotate_mailu_password_missing_config(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: False)
resp = client.post(
"/api/account/mailu/rotate",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 503
def test_reset_wger_password(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.wger, "sync_user", lambda *args, **kwargs: {"status": "ok"})
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["status"] == "ok"
def test_reset_firefly_password(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.firefly, "sync_user", lambda *args, **kwargs: {"status": "ok"})
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["status"] == "ok"
def test_nextcloud_mail_sync(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.nextcloud, "sync_mail", lambda *args, **kwargs: {"status": "ok"})
resp = client.post(
"/api/account/nextcloud/mail/sync",
headers={"Authorization": "Bearer token"},
json={"wait": True},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["status"] == "ok"
def test_nextcloud_mail_sync_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.nextcloud, "sync_mail", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.post(
"/api/account/nextcloud/mail/sync",
headers={"Authorization": "Bearer token"},
json={"wait": True},
)
assert resp.status_code == 502
def test_require_admin_allows_group() -> None:
ctx = AuthContext(username="alice", email="", groups=["admin"], claims={})
app_module._require_admin(ctx)
def test_require_account_access_allows_when_disabled(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=[], claims={})
dummy_settings = type("S", (), {"account_allowed_groups": []})()
monkeypatch.setattr(app_module, "settings", dummy_settings)
app_module._require_account_access(ctx)
def test_access_request_deny_bad_json(monkeypatch) -> None:
ctx = AuthContext(username="bstein", email="", groups=["admin"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.db, "fetchone", lambda *args, **kwargs: {"request_code": "REQ2"})
resp = client.post(
"/api/admin/access/requests/alice/deny",
headers={"Authorization": "Bearer token", "Content-Type": "application/json"},
data="{bad}",
)
assert resp.status_code == 200
def test_rotate_mailu_password_missing_username(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
resp = client.post(
"/api/account/mailu/rotate",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 400
def test_rotate_mailu_password_sync_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.mailu, "sync", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
monkeypatch.setattr(app_module.nextcloud, "sync_mail", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.post(
"/api/account/mailu/rotate",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
payload = resp.json()
assert payload["sync_ok"] is False
assert payload["nextcloud_sync"]["status"] == "error"
def test_rotate_mailu_password_handles_storage_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.storage, "record_task_run", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.post(
"/api/account/mailu/rotate",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
def test_rotate_mailu_password_failure(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")))
resp = client.post(
"/api/account/mailu/rotate",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 502
def test_rotate_mailu_password_http_exception(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(
app_module.keycloak_admin,
"set_user_attribute",
lambda *args, **kwargs: (_ for _ in ()).throw(HTTPException(status_code=409, detail="conflict")),
)
resp = client.post(
"/api/account/mailu/rotate",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 409
def test_wger_reset_missing_username(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 400
def test_wger_reset_unconfigured(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: False)
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 503
def test_wger_reset_uses_mailu_string(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
captured = {}
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(
app_module.keycloak_admin,
"find_user",
lambda username: {"attributes": {"mailu_email": "alias@bstein.dev"}},
)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
def fake_sync_user(username, email, password, wait=True):
captured["email"] = email
return {"status": "ok"}
monkeypatch.setattr(app_module.wger, "sync_user", fake_sync_user)
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
assert captured["email"] == "alias@bstein.dev"
def test_wger_reset_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
monkeypatch.setattr(app_module.wger, "sync_user", lambda *args, **kwargs: {"status": "error"})
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 502
def test_wger_reset_http_exception(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
def raise_http(*_args, **_kwargs):
raise HTTPException(status_code=409, detail="conflict")
monkeypatch.setattr(app_module.wger, "sync_user", raise_http)
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 409
def test_wger_reset_handles_storage_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.wger, "sync_user", lambda *args, **kwargs: {"status": "ok"})
monkeypatch.setattr(
app_module.storage,
"record_task_run",
lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")),
)
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
def test_wger_reset_handles_find_user_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(
app_module.keycloak_admin,
"find_user",
lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("fail")),
)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.wger, "sync_user", lambda *args, **kwargs: {"status": "ok"})
resp = client.post(
"/api/account/wger/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
def test_firefly_reset_missing_username(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 400
def test_firefly_reset_unconfigured(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: False)
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 503
def test_firefly_reset_uses_mailu_string(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
captured = {}
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(
app_module.keycloak_admin,
"find_user",
lambda username: {"attributes": {"mailu_email": "alias@bstein.dev"}},
)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
def fake_sync_user(email, password, wait=True):
captured["email"] = email
return {"status": "ok"}
monkeypatch.setattr(app_module.firefly, "sync_user", fake_sync_user)
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
assert captured["email"] == "alias@bstein.dev"
def test_firefly_reset_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
monkeypatch.setattr(app_module.firefly, "sync_user", lambda *args, **kwargs: {"status": "error"})
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 502
def test_firefly_reset_http_exception(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
def raise_http(*_args, **_kwargs):
raise HTTPException(status_code=409, detail="conflict")
monkeypatch.setattr(app_module.firefly, "sync_user", raise_http)
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 409
def test_firefly_reset_handles_storage_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.keycloak_admin, "find_user", lambda username: {"attributes": {"mailu_email": ["alice@bstein.dev"]}})
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.firefly, "sync_user", lambda *args, **kwargs: {"status": "ok"})
monkeypatch.setattr(
app_module.storage,
"record_task_run",
lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")),
)
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
def test_firefly_reset_handles_find_user_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(
app_module.keycloak_admin,
"find_user",
lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("fail")),
)
monkeypatch.setattr(app_module.keycloak_admin, "set_user_attribute", lambda *args, **kwargs: None)
monkeypatch.setattr(app_module.firefly, "sync_user", lambda *args, **kwargs: {"status": "ok"})
resp = client.post(
"/api/account/firefly/reset",
headers={"Authorization": "Bearer token"},
)
assert resp.status_code == 200
def test_nextcloud_mail_sync_bad_json(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.nextcloud, "sync_mail", lambda *args, **kwargs: {"status": "ok"})
resp = client.post(
"/api/account/nextcloud/mail/sync",
headers={"Authorization": "Bearer token", "Content-Type": "application/json"},
data="{bad}",
)
assert resp.status_code == 200
def test_nextcloud_mail_sync_unconfigured(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: False)
resp = client.post(
"/api/account/nextcloud/mail/sync",
headers={"Authorization": "Bearer token"},
json={"wait": True},
)
assert resp.status_code == 503
def test_nextcloud_mail_sync_missing_username(monkeypatch) -> None:
ctx = AuthContext(username="", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
resp = client.post(
"/api/account/nextcloud/mail/sync",
headers={"Authorization": "Bearer token"},
json={"wait": True},
)
assert resp.status_code == 400
def test_nextcloud_mail_sync_http_exception(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(
app_module.nextcloud,
"sync_mail",
lambda *args, **kwargs: (_ for _ in ()).throw(HTTPException(status_code=409, detail="conflict")),
)
resp = client.post(
"/api/account/nextcloud/mail/sync",
headers={"Authorization": "Bearer token"},
json={"wait": True},
)
assert resp.status_code == 409
def test_nextcloud_mail_sync_handles_storage_error(monkeypatch) -> None:
ctx = AuthContext(username="alice", email="", groups=["dev"], claims={})
client = _client(monkeypatch, ctx)
monkeypatch.setattr(app_module.keycloak_admin, "ready", lambda: True)
monkeypatch.setattr(app_module.nextcloud, "sync_mail", lambda *args, **kwargs: {"status": "ok"})
monkeypatch.setattr(
app_module.storage,
"record_task_run",
lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("fail")),
)
resp = client.post(
"/api/account/nextcloud/mail/sync",
headers={"Authorization": "Bearer token"},
json={"wait": True},
)
assert resp.status_code == 200