Add tests and dedupe nextcloud mail sync
This commit is contained in:
parent
c8b49560b6
commit
0d0216c8f5
@ -10,6 +10,12 @@ if ! command -v jq >/dev/null 2>&1; then
|
|||||||
apt-get update && apt-get install -y jq curl >/dev/null
|
apt-get update && apt-get install -y jq curl >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
account_exists() {
|
||||||
|
# Skip if the account email is already present in the mail app.
|
||||||
|
runuser -u www-data -- php occ mail:account:list 2>/dev/null | grep -Fq " ${1}" || \
|
||||||
|
runuser -u www-data -- php occ mail:account:list 2>/dev/null | grep -Fq "${1} "
|
||||||
|
}
|
||||||
|
|
||||||
token=$(
|
token=$(
|
||||||
curl -s -d "grant_type=password" \
|
curl -s -d "grant_type=password" \
|
||||||
-d "client_id=admin-cli" \
|
-d "client_id=admin-cli" \
|
||||||
@ -31,6 +37,10 @@ echo "${users}" | jq -c '.[]' | while read -r user; do
|
|||||||
email=$(echo "${user}" | jq -r '.email // empty')
|
email=$(echo "${user}" | jq -r '.email // empty')
|
||||||
app_pw=$(echo "${user}" | jq -r '.attributes.mailu_app_password[0] // empty')
|
app_pw=$(echo "${user}" | jq -r '.attributes.mailu_app_password[0] // empty')
|
||||||
[[ -z "${email}" || -z "${app_pw}" ]] && continue
|
[[ -z "${email}" || -z "${app_pw}" ]] && continue
|
||||||
|
if account_exists "${email}"; then
|
||||||
|
echo "Skipping ${email}, already exists"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
echo "Syncing ${email}"
|
echo "Syncing ${email}"
|
||||||
runuser -u www-data -- php occ mail:account:create \
|
runuser -u www-data -- php occ mail:account:create \
|
||||||
"${username}" "${username}" "${email}" \
|
"${username}" "${username}" "${email}" \
|
||||||
|
|||||||
58
scripts/tests/test_dashboards_render_atlas.py
Normal file
58
scripts/tests/test_dashboards_render_atlas.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import importlib.util
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
def load_module():
|
||||||
|
path = pathlib.Path(__file__).resolve().parents[1] / "dashboards_render_atlas.py"
|
||||||
|
spec = importlib.util.spec_from_file_location("dashboards_render_atlas", path)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
assert spec.loader is not None
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_panel_options_and_filterable():
|
||||||
|
mod = load_module()
|
||||||
|
panel = mod.table_panel(
|
||||||
|
1,
|
||||||
|
"test",
|
||||||
|
"metric",
|
||||||
|
{"h": 1, "w": 1, "x": 0, "y": 0},
|
||||||
|
unit="percent",
|
||||||
|
transformations=[{"id": "labelsToFields", "options": {}}],
|
||||||
|
instant=True,
|
||||||
|
options={"showColumnFilters": False},
|
||||||
|
filterable=False,
|
||||||
|
footer={"show": False, "fields": "", "calcs": []},
|
||||||
|
format="table",
|
||||||
|
)
|
||||||
|
assert panel["fieldConfig"]["defaults"]["unit"] == "percent"
|
||||||
|
assert panel["fieldConfig"]["defaults"]["custom"]["filterable"] is False
|
||||||
|
assert panel["options"]["showHeader"] is True
|
||||||
|
assert panel["targets"][0]["format"] == "table"
|
||||||
|
|
||||||
|
|
||||||
|
def test_node_filter_and_expr_helpers():
|
||||||
|
mod = load_module()
|
||||||
|
expr = mod.node_filter("titan-.*")
|
||||||
|
assert "label_replace" in expr
|
||||||
|
cpu_expr = mod.node_cpu_expr("titan-.*")
|
||||||
|
mem_expr = mod.node_mem_expr("titan-.*")
|
||||||
|
assert "node_cpu_seconds_total" in cpu_expr
|
||||||
|
assert "node_memory_MemAvailable_bytes" in mem_expr
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_configmap_writes(tmp_path):
|
||||||
|
mod = load_module()
|
||||||
|
mod.DASHBOARD_DIR = tmp_path / "dash"
|
||||||
|
mod.ROOT = tmp_path
|
||||||
|
uid = "atlas-test"
|
||||||
|
info = {"configmap": tmp_path / "cm.yaml"}
|
||||||
|
data = {"title": "Atlas Test"}
|
||||||
|
mod.write_json(uid, data)
|
||||||
|
mod.render_configmap(uid, info)
|
||||||
|
json_path = mod.DASHBOARD_DIR / f"{uid}.json"
|
||||||
|
assert json_path.exists()
|
||||||
|
content = (tmp_path / "cm.yaml").read_text()
|
||||||
|
assert "kind: ConfigMap" in content
|
||||||
|
assert f"{uid}.json" in content
|
||||||
@ -81,3 +81,101 @@ def test_kc_update_attributes_raises_without_attribute(monkeypatch):
|
|||||||
sync.SESSION = _FakeSession(_FakeResponse({}), missing_attr_resp)
|
sync.SESSION = _FakeSession(_FakeResponse({}), missing_attr_resp)
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
sync.kc_update_attributes("token", {"id": "u1", "username": "u1"}, {"mailu_app_password": "abc"})
|
sync.kc_update_attributes("token", {"id": "u1", "username": "u1"}, {"mailu_app_password": "abc"})
|
||||||
|
|
||||||
|
|
||||||
|
def test_kc_get_users_paginates(monkeypatch):
|
||||||
|
sync = load_sync_module(monkeypatch)
|
||||||
|
|
||||||
|
class _PagedSession:
|
||||||
|
def __init__(self):
|
||||||
|
self.calls = 0
|
||||||
|
|
||||||
|
def post(self, *_, **__):
|
||||||
|
return _FakeResponse({"access_token": "tok"})
|
||||||
|
|
||||||
|
def get(self, *_, **__):
|
||||||
|
self.calls += 1
|
||||||
|
if self.calls == 1:
|
||||||
|
return _FakeResponse([{"id": "u1"}, {"id": "u2"}])
|
||||||
|
return _FakeResponse([]) # stop pagination
|
||||||
|
|
||||||
|
sync.SESSION = _PagedSession()
|
||||||
|
users = sync.kc_get_users("tok")
|
||||||
|
assert [u["id"] for u in users] == ["u1", "u2"]
|
||||||
|
assert sync.SESSION.calls == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_mailu_user_skips_foreign_domain(monkeypatch):
|
||||||
|
sync = load_sync_module(monkeypatch)
|
||||||
|
executed = []
|
||||||
|
|
||||||
|
class _Cursor:
|
||||||
|
def execute(self, sql, params):
|
||||||
|
executed.append((sql, params))
|
||||||
|
|
||||||
|
sync.ensure_mailu_user(_Cursor(), "user@other.com", "pw", "User")
|
||||||
|
assert not executed
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_mailu_user_upserts(monkeypatch):
|
||||||
|
sync = load_sync_module(monkeypatch)
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
class _Cursor:
|
||||||
|
def execute(self, sql, params):
|
||||||
|
captured.update(params)
|
||||||
|
|
||||||
|
sync.ensure_mailu_user(_Cursor(), "user@example.com", "pw", "User Example")
|
||||||
|
assert captured["email"] == "user@example.com"
|
||||||
|
assert captured["localpart"] == "user"
|
||||||
|
# password should be hashed, not the raw string
|
||||||
|
assert captured["password"] != "pw"
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_generates_password_and_upserts(monkeypatch):
|
||||||
|
sync = load_sync_module(monkeypatch)
|
||||||
|
users = [
|
||||||
|
{"id": "u1", "username": "user1", "email": "user1@example.com", "attributes": {}},
|
||||||
|
{"id": "u2", "username": "user2", "email": "user2@example.com", "attributes": {"mailu_app_password": ["keepme"]}},
|
||||||
|
{"id": "u3", "username": "user3", "email": "user3@other.com", "attributes": {}},
|
||||||
|
]
|
||||||
|
updated = []
|
||||||
|
|
||||||
|
class _Cursor:
|
||||||
|
def __init__(self):
|
||||||
|
self.executions = []
|
||||||
|
|
||||||
|
def execute(self, sql, params):
|
||||||
|
self.executions.append(params)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
class _Conn:
|
||||||
|
def __init__(self):
|
||||||
|
self.autocommit = False
|
||||||
|
self._cursor = _Cursor()
|
||||||
|
|
||||||
|
def cursor(self, cursor_factory=None):
|
||||||
|
return self._cursor
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
monkeypatch.setattr(sync, "get_kc_token", lambda: "tok")
|
||||||
|
monkeypatch.setattr(sync, "kc_get_users", lambda token: users)
|
||||||
|
monkeypatch.setattr(sync, "kc_update_attributes", lambda token, user, attrs: updated.append((user["id"], attrs["mailu_app_password"])))
|
||||||
|
conns = []
|
||||||
|
|
||||||
|
def _connect(**kwargs):
|
||||||
|
conn = _Conn()
|
||||||
|
conns.append(conn)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
monkeypatch.setattr(sync.psycopg2, "connect", _connect)
|
||||||
|
|
||||||
|
sync.main()
|
||||||
|
|
||||||
|
# Should attempt two inserts (third user skipped due to domain mismatch)
|
||||||
|
assert len(updated) == 1 # only one missing attr was backfilled
|
||||||
|
assert conns and len(conns[0]._cursor.executions) == 2
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user