Files
PacketStation/tests/server/test_phase8_server_smoke.py
T

141 lines
7.6 KiB
Python
Raw Normal View History

"""Phase 8 server smoke: one small world through every new read (the find bar on the list and the boards, the PM
reports, the exports) so the server agent's shapes are exercised end to end. The detailed checks live in
test_search.py, test_filters.py, test_pm_api.py, test_pm_export.py and test_presence.py."""
from __future__ import annotations
import io
from collections.abc import Iterator
import pytest
from fakedir import FakeDirectory
from pshelpers import app_conn, needs_pg
from pypdf import PdfReader
from shophelpers import Shop, draft, make_unit, ok, open_shop, ready_packet, run, set_running_since, start, to_station
from shophelpers import to_warehouse as send_to_wh
from packetstation.settings import Settings
pytestmark = needs_pg
@pytest.fixture
def shop(pg_settings: Settings, fake_dir: FakeDirectory, dev_visual: None) -> Iterator[Shop]:
yield from open_shop(pg_settings, fake_dir)
def test_find_bar_boards_and_pm_reports(shop: Shop) -> None:
settings = shop.settings
project_id, u1 = make_unit(settings, "L670", "052")
with app_conn(settings) as conn:
u2 = conn.execute(
"INSERT INTO units (project_id, lot, name, status, due) VALUES (%s, '053', 'Unit two', 'open',"
" current_date - 2) RETURNING id",
(project_id,),
).fetchone()[0]
conn.execute("UPDATE projects SET due = current_date + 30 WHERE id = %s", (project_id,))
p_draft = draft(shop, u1, title="Inlet nozzle =SUM(A1)")
p_wh = send_to_wh(shop, draft(shop, u1, drawing="D-7000"))
ok(
shop("wh.wade").post(
f"/api/packets/{p_wh['id']}/wh",
json={"stage": "hold", "reason": "Waiting on purchasing / PO", "expect_stage": "incoming"},
)
)
p_st = start(shop, to_station(shop, ready_packet(shop, u2)))
set_running_since(settings, p_st["id"], 6 * 3600)
with app_conn(settings) as conn:
line = conn.execute("SELECT id FROM bom_lines WHERE packet_id = %s AND item = 2", (p_wh["id"],)).fetchone()[0]
conn.execute(
"INSERT INTO bom_shorts (packet_id, bom_line_id, qty, qty_open, reason, ref, eta)"
" VALUES (%s, %s, 2, 2, 'Not in stock', 'PO-881', current_date - 1)",
(p_wh["id"], line),
)
conn.execute(
"INSERT INTO qc_history (packet_id, at, result, inspector, note, dept_code, snapshot)"
" VALUES (%s, now() - interval '3 days', 'rejected', 'Quinn QC', 'NCR-12 · undercut', '03', '{}')",
(p_st["id"],),
)
co = conn.execute(
"INSERT INTO change_orders (packet_id, co_no, origin, review, ecn, reason, issued_at)"
" VALUES (%s, 1, 'manual', 'approved', 'ECN-77', 'Rev C', now() - interval '4 days') RETURNING id",
(p_st["id"],),
).fetchone()[0]
conn.execute("INSERT INTO co_required_depts (co_id, dept_code) VALUES (%s, '03')", (co,))
run(settings, "UPDATE packets SET wps = 'WPS-99' WHERE id = %s", (p_draft["id"],))
sup = shop("sup.wes")
# The find bar on the list
got = ok(sup.get("/api/packets", params={"q": "lot 52", "project_id": project_id, "facets": 1}))
ids = {c["id"] for c in got["packets"]}
assert p_draft["id"] in ids and p_st["id"] not in ids, ids
assert len(got["facets"]["workers"]) == 1 and len(got["facets"]["stations"]) == 1
assert ok(sup.get("/api/packets", params={"q": "HT-20"}))["packets"][0]["id"] == p_st["id"]
assert [c["id"] for c in ok(sup.get("/api/packets", params={"q": "ECN-77"}))["packets"]] == [p_st["id"]]
assert [c["id"] for c in ok(sup.get("/api/packets", params={"status": "station"}))["packets"]] == [p_st["id"]]
shorts = ok(sup.get("/api/packets", params={"flags": "short"}))["packets"]
assert [c["id"] for c in shorts] == [p_wh["id"]]
mine = ok(sup.get("/api/packets", params={"flags": "mine", "mine_screen": "supervisor"}))["packets"]
assert {c["id"] for c in mine} == {p_draft["id"], p_wh["id"], p_st["id"]}
over = ok(sup.get("/api/packets", params={"flags": "overdue"}))["packets"]
assert [c["id"] for c in over] == [p_st["id"]]
for sort in ("updated", "project", "due", "status", "dept"):
assert len(ok(sup.get("/api/packets", params={"sort": sort, "project_id": project_id}))["packets"]) == 3
hand = ok(sup.get("/api/packets", params={"flag": "hand"}))["packets"]
assert {c["id"] for c in hand} == {p_draft["id"]}
# The boards keep their KPIs on the project scope
b = ok(sup.get("/api/shop/board", params={"q": "nothing-matches-this"}))
assert b["kpis"]["at_stations"] == 1 and not b["depts"][0]["stations"][0]["packets"]
assert "facets" in b
wh = ok(shop("wh.wade").get("/api/warehouse/board", params={"flags": "short"}))
assert [c["id"] for c in wh["columns"]["hold"]] == [p_wh["id"]] and "facets" in wh
qc = ok(shop("qc.quinn").get("/api/qc/home", params={"q": "zzz"}))
assert qc["kpis"]["waiting"] == 0 and "facets" in qc
stall = ok(shop("weld.andy").get("/api/shop/stall", params={"station_id": shop.stall(), "flags": "mine"}))
assert [c["id"] for c in stall["mine"]] == [p_st["id"]]
mine = ok(shop("weld.andy").get("/api/stall/mine")) # change C11: no station picked
assert [c["id"] for c in mine["mine"]] == [p_st["id"]] and mine["mine"][0]["station_id"] == shop.stall()
# Project Management
pmc = shop("pm.paula")
ov = ok(pmc.get("/api/pm/overview", params={"period": "30"}))
k = {x["key"]: x["n"] for x in ov["kpis"]}
assert k["open"] == 3 and k["holds"] == 1 and k["shorts"] == 1 and k["cos"] == 1 and k["rejects"] == 1, k
assert k["late_units"] == 1, k
assert ov["scope"]["label"] == "All projects" and ov["summary"][0].startswith("3 open packets")
assert sum(s["count"] for s in ov["pipeline"]) == 3
assert [(r["cells"][0], r["health"]) for r in ov["attention"]["rows"]] == [("L670052", "risk"), ("L670053", "late")]
pr = ok(pmc.get("/api/pm/projects", params={"expand": "all"}))
assert [r["lvl"] for r in pr["tables"][0]["rows"]][:3] == [0, 1, 1]
ag = ok(pmc.get("/api/pm/aging"))
assert [t["key"] for t in ag["tables"]] == ["aging_steps", "aging_open", "aging_history"]
hr = ok(pmc.get("/api/pm/hours"))
over_rows = hr["tables"][3]["rows"]
# change C1: the timer is over its target but there are no ticket hours, so it is not over target
assert over_rows == [], hr
ql = ok(pmc.get("/api/pm/quality"))
assert [len(t["rows"]) for t in ql["tables"]][:4] == [1, 1, 1, 1], ql
fd = ok(pmc.get("/api/pm/find", params={"sort": "project", "project_id": project_id}))
assert fd["scope"]["label"] == "Project L670" and len(fd["packets"]) == 3
r = pmc.get("/api/pm/export.csv", params={"table": "all"})
assert r.status_code == 200, r.text
text = r.content.decode("utf-8")
assert text.startswith("\ufeffPESCO Packet Station — All reports\r\n")
assert "attachment" in r.headers["content-disposition"]
assert pmc.get("/api/pm/export.csv", params={"table": "nope"}).status_code == 404
r = pmc.get("/api/pm/report.pdf")
assert r.status_code == 200 and r.content.startswith(b"%PDF")
reader = PdfReader(io.BytesIO(r.content))
assert len(reader.pages) > 5
assert "PRODUCTION STATUS REPORT" in reader.pages[0].extract_text()
assert all("Controlled copy" in pg.extract_text() for pg in reader.pages)
with app_conn(settings) as conn:
kinds = [x[0] for x in conn.execute("SELECT kind FROM print_log ORDER BY id")]
acts = [x[0] for x in conn.execute("SELECT action FROM audit_events WHERE action = 'report exported'")]
assert kinds == ["report-csv", "report-pdf"] and len(acts) == 2
assert shop("clerk.clara").get("/api/pm/overview").status_code == 403 # change C10: PM and Admin only