179 lines
7.0 KiB
Python
179 lines
7.0 KiB
Python
from __future__ import annotations
|
|
|
|
"""Tests for per-user Kubernetes sync Job adapters."""
|
|
|
|
import pytest
|
|
|
|
from atlas_portal import firefly_user_sync, nextcloud_mail_sync, wger_user_sync
|
|
|
|
|
|
def _cronjob_template() -> dict:
|
|
"""Build a CronJob payload shaped like the templates used in the cluster."""
|
|
|
|
return {
|
|
"spec": {
|
|
"jobTemplate": {
|
|
"spec": {
|
|
"template": {
|
|
"spec": {
|
|
"containers": [
|
|
{
|
|
"name": "worker",
|
|
"env": [
|
|
{"name": "ONLY_USERNAME", "value": "old"},
|
|
{"name": "FIREFLY_USER_EMAIL", "value": "old"},
|
|
{"name": "WGER_USERNAME", "value": "old"},
|
|
],
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("module", "namespace_attr", "cronjob_attr", "timeout_attr", "args", "expected_env"),
|
|
[
|
|
(
|
|
nextcloud_mail_sync,
|
|
"NEXTCLOUD_NAMESPACE",
|
|
"NEXTCLOUD_MAIL_SYNC_CRONJOB",
|
|
"NEXTCLOUD_MAIL_SYNC_WAIT_TIMEOUT_SEC",
|
|
("alice",),
|
|
{"ONLY_USERNAME": "alice"},
|
|
),
|
|
(
|
|
firefly_user_sync,
|
|
"FIREFLY_NAMESPACE",
|
|
"FIREFLY_USER_SYNC_CRONJOB",
|
|
"FIREFLY_USER_SYNC_WAIT_TIMEOUT_SEC",
|
|
("alice", "alice@example.dev", "pw"),
|
|
{"FIREFLY_USER_EMAIL": "alice@example.dev", "FIREFLY_USER_PASSWORD": "pw"},
|
|
),
|
|
(
|
|
wger_user_sync,
|
|
"WGER_NAMESPACE",
|
|
"WGER_USER_SYNC_CRONJOB",
|
|
"WGER_USER_SYNC_WAIT_TIMEOUT_SEC",
|
|
("alice", "alice@example.dev", "pw"),
|
|
{"WGER_USERNAME": "alice", "WGER_EMAIL": "alice@example.dev", "WGER_PASSWORD": "pw"},
|
|
),
|
|
],
|
|
)
|
|
def test_user_sync_modules_render_jobs_and_trigger(monkeypatch, module, namespace_attr, cronjob_attr, timeout_attr, args, expected_env) -> None:
|
|
monkeypatch.setattr(module.settings, namespace_attr, "apps")
|
|
monkeypatch.setattr(module.settings, cronjob_attr, "sync-cron")
|
|
monkeypatch.setattr(module.settings, timeout_attr, 0)
|
|
monkeypatch.setattr(module.time, "time", lambda: 1000)
|
|
|
|
posted: list[dict] = []
|
|
|
|
def fake_get_json(path: str) -> dict:
|
|
if "cronjobs" in path:
|
|
return _cronjob_template()
|
|
return {"status": {"conditions": [{"type": "Complete", "status": "True"}]}}
|
|
|
|
def fake_post_json(path: str, payload: dict) -> dict:
|
|
posted.append(payload)
|
|
return {"metadata": {"name": payload["metadata"]["name"]}}
|
|
|
|
monkeypatch.setattr(module, "get_json", fake_get_json)
|
|
monkeypatch.setattr(module, "post_json", fake_post_json)
|
|
|
|
result = module.trigger(*args, wait=True)
|
|
|
|
assert result["status"] in {"ok", "running"}
|
|
env = posted[0]["spec"]["template"]["spec"]["containers"][0]["env"]
|
|
env_map = {item["name"]: item["value"] for item in env}
|
|
for key, value in expected_env.items():
|
|
assert env_map[key] == value
|
|
assert module._job_succeeded({"status": {"succeeded": 1}})
|
|
assert module._job_failed({"status": {"failed": 1}})
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("module", "namespace_attr", "cronjob_attr", "timeout_attr", "args"),
|
|
[
|
|
(
|
|
nextcloud_mail_sync,
|
|
"NEXTCLOUD_NAMESPACE",
|
|
"NEXTCLOUD_MAIL_SYNC_CRONJOB",
|
|
"NEXTCLOUD_MAIL_SYNC_WAIT_TIMEOUT_SEC",
|
|
("alice",),
|
|
),
|
|
(
|
|
firefly_user_sync,
|
|
"FIREFLY_NAMESPACE",
|
|
"FIREFLY_USER_SYNC_CRONJOB",
|
|
"FIREFLY_USER_SYNC_WAIT_TIMEOUT_SEC",
|
|
("alice", "alice@example.dev", "pw"),
|
|
),
|
|
(
|
|
wger_user_sync,
|
|
"WGER_NAMESPACE",
|
|
"WGER_USER_SYNC_CRONJOB",
|
|
"WGER_USER_SYNC_WAIT_TIMEOUT_SEC",
|
|
("alice", "alice@example.dev", "pw"),
|
|
),
|
|
],
|
|
)
|
|
def test_user_sync_modules_cover_edge_paths(monkeypatch, module, namespace_attr, cronjob_attr, timeout_attr, args) -> None:
|
|
assert module._safe_name_fragment("!!!") == "user"
|
|
assert module._job_succeeded({"status": {"conditions": [None, {"type": "Complete", "status": "True"}]}})
|
|
assert not module._job_succeeded({"status": {"conditions": [{"type": "Complete", "status": "False"}]}})
|
|
assert module._job_failed({"status": {"conditions": [None, {"type": "Failed", "status": "True"}]}})
|
|
assert not module._job_failed({"status": {"conditions": [{"type": "Failed", "status": "False"}]}})
|
|
|
|
cronjob = _cronjob_template()
|
|
container = cronjob["spec"]["jobTemplate"]["spec"]["template"]["spec"]["containers"][0]
|
|
container["env"] = "not-a-list"
|
|
job = module._job_from_cronjob(cronjob, *args)
|
|
assert job["spec"]["template"]["spec"]["containers"][0]["env"]
|
|
|
|
monkeypatch.setattr(module.settings, namespace_attr, "apps")
|
|
monkeypatch.setattr(module.settings, cronjob_attr, "sync-cron")
|
|
monkeypatch.setattr(module.settings, timeout_attr, 5)
|
|
monkeypatch.setattr(module.time, "sleep", lambda *_: None)
|
|
|
|
with pytest.raises(RuntimeError, match="missing username"):
|
|
module.trigger("", *args[1:])
|
|
|
|
if module in {firefly_user_sync, wger_user_sync}:
|
|
with pytest.raises(RuntimeError, match="missing password"):
|
|
module.trigger(args[0], args[1], "")
|
|
monkeypatch.setattr(module.settings, namespace_attr, "")
|
|
with pytest.raises(RuntimeError, match="not configured"):
|
|
module.trigger(*args)
|
|
monkeypatch.setattr(module.settings, namespace_attr, "apps")
|
|
|
|
def cron_then_complete(path: str) -> dict:
|
|
if "cronjobs" in path:
|
|
return _cronjob_template()
|
|
return {"status": {"conditions": [{"type": "Complete", "status": "True"}]}}
|
|
|
|
monkeypatch.setattr(module, "get_json", cron_then_complete)
|
|
monkeypatch.setattr(module, "post_json", lambda path, payload: {})
|
|
assert module.trigger(*args, wait=False)["status"] == "queued"
|
|
|
|
monkeypatch.setattr(module, "post_json", lambda path, payload: {"metadata": {"name": ""}})
|
|
with pytest.raises(RuntimeError, match="job name missing"):
|
|
module.trigger(*args, wait=True)
|
|
|
|
monkeypatch.setattr(module, "post_json", lambda path, payload: {"metadata": {"name": payload["metadata"]["name"]}})
|
|
clock = iter([0, 1, 2])
|
|
monkeypatch.setattr(module.time, "time", lambda: next(clock))
|
|
assert module.trigger(*args, wait=True)["status"] == "ok"
|
|
|
|
def cron_then_failed(path: str) -> dict:
|
|
if "cronjobs" in path:
|
|
return _cronjob_template()
|
|
return {"status": {"conditions": [{"type": "Failed", "status": "True"}]}}
|
|
|
|
clock = iter([0, 1, 2])
|
|
monkeypatch.setattr(module.time, "time", lambda: next(clock))
|
|
monkeypatch.setattr(module, "get_json", cron_then_failed)
|
|
assert module.trigger(*args, wait=True)["status"] == "error"
|