Files
DashBoard/tests/test_wip_hold_pages_integration.py
egg be22571421 feat(wip): preserve filters between Overview and Detail with thundering-herd fix
URL is now single source of truth for filter state (workorder, lotid,
package, type, status) across WIP Overview and Detail pages. Drill-down
carries all filters + status; back button dynamically reflects Detail
changes. Backend Detail API now supports pj_type filter parameter.

Harden concurrency: add pagehide abort for MPA navigation, double-check
locking on Redis JSON parse and snapshot build to prevent thread pool
saturation during rapid page switching. Fix watchdog setsid and PID
discovery. Fix test_realtime_equipment_cache RUNCARDLOTID field mismatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 07:22:48 +08:00

153 lines
5.1 KiB
Python

# -*- coding: utf-8 -*-
"""Integration tests for WIP Overview / WIP Detail / Hold Detail page contracts."""
from __future__ import annotations
import json
from unittest.mock import patch
import pytest
import mes_dashboard.core.database as db
from mes_dashboard.app import create_app
@pytest.fixture
def client():
"""Create a test client with isolated DB engine state."""
db._ENGINE = None
app = create_app("testing")
app.config["TESTING"] = True
return app.test_client()
def test_wip_pages_render_vite_assets(client):
"""Core WIP/Hold pages should render Vite bundles."""
overview = client.get("/wip-overview")
detail = client.get("/wip-detail")
hold = client.get("/hold-detail?reason=YieldLimit")
assert overview.status_code == 200
assert detail.status_code == 200
assert hold.status_code == 200
overview_html = overview.data.decode("utf-8")
detail_html = detail.data.decode("utf-8")
hold_html = hold.data.decode("utf-8")
assert "/static/dist/wip-overview.js" in overview_html
assert "/static/dist/wip-detail.js" in detail_html
assert "/static/dist/hold-detail.js" in hold_html
def test_wip_overview_and_detail_status_parameter_contract(client):
"""Status/type params should be accepted across overview and detail APIs."""
with (
patch("mes_dashboard.routes.wip_routes.get_wip_matrix") as mock_matrix,
patch("mes_dashboard.routes.wip_routes.get_wip_detail") as mock_detail,
):
mock_matrix.return_value = {
"workcenters": [],
"packages": [],
"matrix": {},
"workcenter_totals": {},
"package_totals": {},
"grand_total": 0,
}
mock_detail.return_value = {
"workcenter": "TMTT",
"summary": {
"total_lots": 0,
"on_equipment_lots": 0,
"waiting_lots": 0,
"hold_lots": 0,
},
"specs": [],
"lots": [],
"pagination": {"page": 1, "page_size": 100, "total_count": 0, "total_pages": 1},
"sys_date": None,
}
matrix_resp = client.get("/api/wip/overview/matrix?type=PJA3460&status=queue")
detail_resp = client.get("/api/wip/detail/TMTT?type=PJA3460&status=queue&page=1&page_size=100")
assert matrix_resp.status_code == 200
assert detail_resp.status_code == 200
assert json.loads(matrix_resp.data)["success"] is True
assert json.loads(detail_resp.data)["success"] is True
mock_matrix.assert_called_once_with(
include_dummy=False,
workorder=None,
lotid=None,
status="QUEUE",
hold_type=None,
package=None,
pj_type="PJA3460",
)
mock_detail.assert_called_once_with(
workcenter="TMTT",
package=None,
pj_type="PJA3460",
status="QUEUE",
hold_type=None,
workorder=None,
lotid=None,
include_dummy=False,
page=1,
page_size=100,
)
def test_hold_detail_api_contract_flow(client):
"""Hold detail summary/distribution/lots should all accept the same reason."""
with (
patch("mes_dashboard.routes.hold_routes.get_hold_detail_summary") as mock_summary,
patch("mes_dashboard.routes.hold_routes.get_hold_detail_distribution") as mock_distribution,
patch("mes_dashboard.routes.hold_routes.get_hold_detail_lots") as mock_lots,
):
mock_summary.return_value = {
"totalLots": 10,
"totalQty": 1000,
"avgAge": 1.2,
"maxAge": 5.0,
"workcenterCount": 2,
}
mock_distribution.return_value = {
"byWorkcenter": [],
"byPackage": [],
"byAge": [],
}
mock_lots.return_value = {
"lots": [],
"pagination": {"page": 1, "perPage": 50, "total": 0, "totalPages": 1},
"filters": {"workcenter": None, "package": None, "ageRange": None},
}
reason = "YieldLimit"
summary_resp = client.get(f"/api/wip/hold-detail/summary?reason={reason}")
dist_resp = client.get(f"/api/wip/hold-detail/distribution?reason={reason}")
lots_resp = client.get(
f"/api/wip/hold-detail/lots?reason={reason}&workcenter=DA&package=DIP-B&age_range=1-3&page=2&per_page=80"
)
assert summary_resp.status_code == 200
assert dist_resp.status_code == 200
assert lots_resp.status_code == 200
assert json.loads(summary_resp.data)["success"] is True
assert json.loads(dist_resp.data)["success"] is True
assert json.loads(lots_resp.data)["success"] is True
mock_summary.assert_called_once_with(reason=reason, include_dummy=False)
mock_distribution.assert_called_once_with(reason=reason, include_dummy=False)
mock_lots.assert_called_once_with(
reason=reason,
workcenter="DA",
package="DIP-B",
age_range="1-3",
include_dummy=False,
page=2,
page_size=80,
)