"""Admin departments, profiles, checklists and directory flags.""" from __future__ import annotations import copy from collections.abc import Iterator import pytest from fakedir import FakeDirectory from fastapi.testclient import TestClient from pshelpers import app_conn, needs_pg, sign_in from psycopg.types.json import Jsonb from packetstation.auth import groups from packetstation.domain.checklists import DEFAULT_TEMPLATES from packetstation.settings import Settings pytestmark = needs_pg @pytest.fixture(autouse=True) def restore_group_map() -> Iterator[None]: yield groups.use_departments(groups.DEPARTMENTS) def _actions(settings: Settings, like: str) -> list[tuple[str, dict]]: with app_conn(settings) as conn: return [ (r[0], r[1]) for r in conn.execute( "SELECT action, detail FROM audit_events WHERE action LIKE %s ORDER BY id", (like,) ).fetchall() ] def test_departments(pg_client: TestClient, pg_settings: Settings) -> None: sign_in(pg_client, "admin.ann") listed = pg_client.get("/api/admin/departments").json() assert [d["code"] for d in listed][:5] == ["CL", "WH", "QC", "PM", "01"] and len(listed) == 18 weld = next(d for d in listed if d["code"] == "03") assert weld["weld"] is True and weld["kind"] == "shop" and len(weld["stations"]) == 6 assert [g["name"] for g in weld["groups"]] == [ "PS-03-WeldParts-Worker", "PS-03-WeldParts-Lead", "PS-03-WeldParts-Supervisor", ] assert all(g["in_ou"] is True for g in weld["groups"]) elec = next(d for d in listed if d["code"] == "14") assert {g["name"]: g["in_ou"] for g in elec["groups"]}["PS-14-Electrical-Lead"] is False pm = next(d for d in listed if d["code"] == "PM") assert [g["name"] for g in pm["groups"]] == ["PS-ProjectMgmt"] # Code and slug can never change; extra fields are refused for body in ({"code": "99"}, {"slug": "Other"}): r = pg_client.patch("/api/admin/departments/03", json=body) assert r.status_code == 422, body assert pg_client.patch("/api/admin/departments/ZZ", json={"name": "x"}).status_code == 404 st = weld["stations"] r = pg_client.patch( "/api/admin/departments/03", json={ "name": " Weld Parts Shop ", "stations": [ {"id": st[0]["id"], "name": st[1]["name"], "sort": 1, "active": True}, {"id": st[1]["id"], "name": st[0]["name"], "sort": 2, "active": False}, {"name": "Stall 7", "sort": 7, "active": True}, ], }, ) assert r.status_code == 200, r.text got = r.json() assert got["name"] == "Weld Parts Shop" and len(got["stations"]) == 7 # nothing deleted by_id = {s["id"]: s for s in got["stations"]} assert by_id[st[0]["id"]]["name"] == "Stall 2" and by_id[st[1]["id"]]["active"] is False dup = pg_client.patch( "/api/admin/departments/03", json={"stations": [{"name": "stall 3", "sort": 9, "active": True}]} ) assert dup.status_code == 422 and dup.json()["code"] == "duplicate_station" other = pg_client.patch( "/api/admin/departments/03", json={"stations": [{"id": listed[5]["stations"][0]["id"], "name": "X", "sort": 1, "active": True}]}, ) assert other.status_code == 422 # Deactivated stations drop out of the public list pub = next(d for d in pg_client.get("/api/departments").json() if d["code"] == "03") assert st[1]["id"] not in {s["id"] for s in pub["stations"]} and pub["name"] == "Weld Parts Shop" # Add a department: the three group names IT must create body = { "code": "15", "slug": "Machining", "name": "Machining", "kind": "shop", "weld": False, "supervisor_group": True, "stations": ["Mill 1", "Lathe 1"], } r = pg_client.post("/api/admin/departments", json=body) assert r.status_code == 201, r.text out = r.json() assert out["groups_to_create"] == ["PS-15-Machining-Worker", "PS-15-Machining-Lead", "PS-15-Machining-Supervisor"] assert out["department"]["sort"] == 190 and out["department"]["created_at"] assert [s["name"] for s in out["department"]["stations"]] == ["Mill 1", "Lathe 1"] assert all(g["in_ou"] is False for g in out["department"]["groups"]) # The group map now knows the new groups assert groups.role_for_group("PS-15-Machining-Lead") is not None assert pg_client.post("/api/admin/departments", json=body).json()["code"] == "code_taken" r = pg_client.post("/api/admin/departments", json={**body, "code": "16", "slug": "machining"}) assert r.status_code == 409 and r.json()["code"] == "slug_taken" for bad in ({"code": "1"}, {"code": "abc"}, {"slug": "9Bad"}, {"slug": "Has Space"}): assert pg_client.post("/api/admin/departments", json={**body, "code": "17", **bad}).status_code == 422, bad r = pg_client.post("/api/admin/departments", json={**body, "code": "17", "slug": "Front", "kind": "support"}) assert r.status_code == 422 and r.json()["code"] == "support_fixed" acts = _actions(pg_settings, "admin.department%") assert [a for a, _ in acts] == ["admin.department_updated", "admin.department_added"] assert acts[0][1]["before"]["name"] == "Weld Parts" and acts[0][1]["after"]["name"] == "Weld Parts Shop" assert acts[1][1]["groups_to_create"] == out["groups_to_create"] def test_profiles_and_flags(pg_client: TestClient, pg_settings: Settings, fake_dir: FakeDirectory) -> None: for sam in ("weld.andy", "lead.lou", "dup.dave", "dup.dina", "noemp.nora"): sign_in(pg_client, sam) andy, lou = str(fake_dir.user("weld.andy").guid), str(fake_dir.user("lead.lou").guid) sign_in(pg_client, "admin.ann") stations = {s["name"]: s["id"] for s in pg_client.get("/api/departments").json()[6]["stations"]} assert pg_client.get("/api/departments").json()[6]["code"] == "03" r = pg_client.patch( f"/api/admin/users/{andy}/profile", json={"welder_stamp": "AW-3", "station_limits": {"03": [stations["Stall 1"], stations["Stall 2"]]}}, ) assert r.status_code == 200, r.text assert r.json()["welder_stamp"] == "AW-3" and r.json()["station_limits"] == { "03": [stations["Stall 1"], stations["Stall 2"]] } r = pg_client.patch(f"/api/admin/users/{lou}/profile", json={"welder_stamp": "aw-3"}) assert r.status_code == 409 and r.json()["code"] == "stamp_taken" for bad in ("x" * 13, "A B", "A_1"): r = pg_client.patch(f"/api/admin/users/{lou}/profile", json={"welder_stamp": bad}) assert r.status_code == 422, bad r = pg_client.patch(f"/api/admin/users/{andy}/profile", json={"station_limits": {"03": [999999]}}) assert r.status_code == 422 and r.json()["code"] == "unknown_station" r = pg_client.patch(f"/api/admin/users/{andy}/profile", json={"station_limits": {"ZZ": []}}) assert r.status_code == 422 and r.json()["code"] == "unknown_department" assert pg_client.patch(f"/api/admin/users/{andy}/profile", json={"active": False}).status_code == 422 row = next(u for u in pg_client.get("/api/admin/users").json() if u["id"] == andy) assert row["welder_stamp"] == "AW-3" and row["profile_active"] is True assert row["station_limits"] == {"03": [stations["Stall 1"], stations["Stall 2"]]} assert "directory_flags" in row and "groups" not in row["directory_flags"] detail = pg_client.get(f"/api/admin/users/{andy}").json() assert detail["welder_stamp"] == "AW-3" # The welder picker honours profile_active people = {p["id"] for p in pg_client.get("/api/departments/03/people").json()} assert andy in people r = pg_client.patch( f"/api/admin/users/{andy}/profile", json={"profile_active": False, "station_limits": {"03": []}} ) assert r.status_code == 200 and r.json()["profile_active"] is False and r.json()["station_limits"] == {} assert andy not in {p["id"] for p in pg_client.get("/api/departments/03/people").json()} r = pg_client.patch(f"/api/admin/users/{andy}/profile", json={"welder_stamp": None}) assert r.json()["welder_stamp"] is None acts = _actions(pg_settings, "admin.profile%") assert [a for a, _ in acts] == ["admin.profile_updated"] * 3 assert acts[0][1]["before"]["welder_stamp"] is None and acts[0][1]["after"]["welder_stamp"] == "AW-3" # Directory flags as the reconcile writes them with app_conn(pg_settings) as conn: conn.execute( "UPDATE users SET directory_flags = %s WHERE sam_account IN ('dup.dave', 'dup.dina')", (Jsonb({"emp_duplicate": True}),), ) conn.execute( "UPDATE users SET directory_flags = %s WHERE sam_account = 'noemp.nora'", (Jsonb({"emp_missing": True}),) ) flags = pg_client.get("/api/admin/directory-flags").json() assert [m["sam_account"] for m in flags["missing"]] == ["noemp.nora"] assert flags["duplicate"][0]["emp_no"] == "9001" assert sorted(u["sam_account"] for u in flags["duplicate"][0]["users"]) == ["dup.dave", "dup.dina"] assert flags["checked_at"] is not None def test_checklists_cas(pg_client: TestClient, pg_settings: Settings) -> None: sign_in(pg_client, "admin.ann") got = pg_client.get("/api/admin/checklists").json() assert got["defaults"] == DEFAULT_TEMPLATES and set(got["value"]) == {"weld", "plain"} version = got["version"] value = copy.deepcopy(got["value"]) value["plain"].append({"group": "When done", "key": "tag", "text": "Tag on {drawing} rev {rev}"}) r = pg_client.put("/api/admin/checklists", json={"value": value, "version": version}) assert r.status_code == 200, r.text assert r.json()["version"] == version + 1 and r.json()["value"]["plain"][-1]["key"] == "tag" stale = pg_client.put("/api/admin/checklists", json={"value": value, "version": version}) assert stale.status_code == 409 and stale.json()["current"]["version"] == version + 1 def bad(change: dict) -> dict: v = copy.deepcopy(value) v["weld"][0] = {**v["weld"][0], **change} r = pg_client.put("/api/admin/checklists", json={"value": v, "version": version + 1}) assert r.status_code == 422, change return r.json() assert "unknown placeholder {stamp}" in bad({"text": "Stamp {stamp}"})["detail"] assert "brace" in bad({"text": "Open { brace"})["detail"] assert "key" in bad({"key": "Bad-Key"})["detail"] assert "used twice" in bad({"key": "heat"})["detail"] assert "group is empty" in bad({"group": " "})["detail"] assert "text is empty" in bad({"text": ""})["detail"] too_many = copy.deepcopy(value) too_many["plain"] = [{"group": "G", "key": f"k{i}", "text": "t"} for i in range(41)] assert pg_client.put("/api/admin/checklists", json={"value": too_many, "version": version + 1}).status_code == 422 empty = {"weld": value["weld"], "plain": []} assert pg_client.put("/api/admin/checklists", json={"value": empty, "version": version + 1}).status_code == 422 acts = _actions(pg_settings, "admin.checklists%") assert len(acts) == 1 and acts[0][1]["after"]["plain"][-1]["key"] == "tag" assert len(acts[0][1]["before"]["plain"]) == len(value["plain"]) - 1