Files
PacketStation/tests/server/test_admin_org.py
T

224 lines
11 KiB
Python
Raw Normal View History

"""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