Spaces:
Configuration error
Configuration error
File size: 5,766 Bytes
546c207 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | """End-to-end checks of the Streamlit app, run headlessly via AppTest.
These drive the real `app.py` script: type into the box, submit, inspect what
was rendered. They are deliberately few -- enough to prove the search path,
the multi-occurrence path and the error paths work.
"""
import pytest
from spareparts import config
pytest.importorskip("streamlit")
from streamlit.testing.v1 import AppTest # noqa: E402
pytestmark = pytest.mark.skipif(
not config.DB_PATH.exists(), reason="run `python -m spareparts.build_index` first"
)
APP = str(config.PROJECT_ROOT / "app.py")
def _search(term: str, timeout: int = 60) -> AppTest:
app = AppTest.from_file(APP, default_timeout=timeout).run()
app.text_input[0].set_value(term)
app.button[0].click().run()
return app
def _text(app: AppTest) -> str:
parts = [el.value for el in app.markdown] + [el.value for el in app.caption]
parts += [el.value for el in app.subheader] + [el.value for el in app.warning]
parts += [el.value for el in app.error] + [el.value for el in app.info]
return "\n".join(str(p) for p in parts)
def test_app_starts_without_a_search():
app = AppTest.from_file(APP, default_timeout=60).run()
assert not app.exception
assert app.title[0].value == "Machine Part Finder"
assert app.text_input[0].label == "Part number"
def test_valid_part_shows_metadata_and_an_image():
app = _search("30642")
assert not app.exception
body = _text(app)
assert "30642" in body
assert "worm,cpl." in body # canonical English name
assert "Motor-drive" in body # catalogue section title
assert "Located" in body # localization method, stated plainly
# PDF page and section number are shown as distinct facts.
metrics = {m.label: m.value for m in app.metric}
assert metrics["PDF page"] == "34"
assert metrics["Catalogue section"] == "07"
assert len(app.image) == 1, "the annotated diagram should be displayed"
def test_unknown_part_is_reported_not_crashed():
app = _search("99999")
assert not app.exception
assert app.error, "an unknown part must produce an error message"
assert "not found in the catalogue" in app.error[0].value
assert not app.image
@pytest.mark.parametrize("term", ["", " ", "abc", "!!!"])
def test_empty_and_non_numeric_input_is_handled(term):
app = _search(term)
assert not app.exception
assert not app.image
if term.strip():
assert app.warning or app.error
def test_messy_input_is_normalized_then_found():
app = _search(" part no. 30642 ")
assert not app.exception
assert not app.error
assert "worm,cpl." in _text(app)
def test_multiple_occurrences_offer_a_selector():
app = _search("73290") # drawn on ten catalogue pages
assert not app.exception
assert app.radio, "a part on several pages must offer a choice"
options = app.radio[0].options
assert len(options) > 1
assert all(o.startswith("Occurrence ") for o in options)
assert "PDF page" in options[0]
# One page is shown at a time, and it is one the user chose.
assert len(app.image) == 1
def test_selecting_another_occurrence_switches_page():
app = _search("73290")
first = {m.label: m.value for m in app.metric}["PDF page"]
app.radio[0].set_value(app.radio[0].options[2]).run()
assert not app.exception
second = {m.label: m.value for m in app.metric}["PDF page"]
assert first != second
def test_low_confidence_result_is_shown_as_approximate():
"""A conservative fallback must be visible as such, not dressed up."""
from spareparts import index_db
conn = index_db.connect()
try:
row = conn.execute(
"SELECT l.part_no FROM labels l JOIN components c ON c.label_id = l.id"
" WHERE c.method = 'capped' AND l.part_no IN"
" (SELECT part_no FROM labels GROUP BY part_no HAVING COUNT(*) = 1)"
" LIMIT 1"
).fetchone()
finally:
conn.close()
app = _search(row[0])
assert not app.exception
body = _text(app)
assert "Approximate" in body
assert "Confidence" in body
def test_listed_but_not_drawn_part_shows_the_page_and_says_so():
app = _search("70248") # listed on page 39, not drawn there
assert not app.exception
assert not app.error
assert app.warning, "the user must be told why there is no highlight"
assert "no callout" in app.warning[0].value
assert len(app.image) == 1, "the page itself should still be shown"
def test_no_ocr_engine_is_loaded_during_a_search():
"""Definition of done: searching must not run OCR or localization.
Checked in a subprocess so that nothing another test imported can mask it.
"""
import subprocess
import sys
import textwrap
script = textwrap.dedent(
"""
import sys
from streamlit.testing.v1 import AppTest
app = AppTest.from_file(sys.argv[1], default_timeout=60).run()
app.text_input[0].set_value("30642")
app.button[0].click().run()
assert not app.exception, app.exception
banned = [m for m in sys.modules
if "rapidocr" in m or m.startswith("spareparts.ocr")
or m.startswith("spareparts.localizer")]
print("LOADED:" + ",".join(sorted(banned)))
"""
)
result = subprocess.run(
[sys.executable, "-c", script, APP],
capture_output=True, text=True, cwd=str(config.PROJECT_ROOT), timeout=300,
)
assert result.returncode == 0, result.stderr[-2000:]
line = next(l for l in result.stdout.splitlines() if l.startswith("LOADED:"))
assert line == "LOADED:", f"search pulled in offline-only modules: {line}"
|