solidprivacy commited on
Commit
eba3d22
·
1 Parent(s): 1f00f8d

test(ui): execute cross-mode export fail-closed transitions

Browse files
tests/test_premium_streamlit_export_gate_app.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from streamlit.testing.v1 import AppTest
6
+
7
+
8
+ REPO_ROOT = Path(__file__).resolve().parents[1]
9
+ APP_PATH = REPO_ROOT / "tests" / "streamlit_apps" / "premium_export_gate_flow_app.py"
10
+ PRODUCTION_APP_PATH = REPO_ROOT / "presidio_streamlit.py"
11
+
12
+
13
+ def _assert_no_exception(at: AppTest) -> None:
14
+ assert len(at.exception) == 0, [item.value for item in at.exception]
15
+
16
+
17
+ def _values(elements) -> list[str]:
18
+ return [str(getattr(element, "value", "")) for element in elements]
19
+
20
+
21
+ def _markdown(at: AppTest, prefix: str) -> str:
22
+ for value in _values(at.markdown):
23
+ if value.startswith(prefix):
24
+ return value
25
+ raise AssertionError(f"Missing markdown prefix {prefix!r}: {_values(at.markdown)!r}")
26
+
27
+
28
+ def _radio(at: AppTest, label: str):
29
+ for widget in at.radio:
30
+ if widget.label == label:
31
+ return widget
32
+ raise AssertionError(f"Missing radio {label!r}")
33
+
34
+
35
+ def _selectbox(at: AppTest, label: str):
36
+ for widget in at.selectbox:
37
+ if widget.label == label:
38
+ return widget
39
+ raise AssertionError(f"Missing selectbox {label!r}")
40
+
41
+
42
+ def _button(at: AppTest, label: str):
43
+ for widget in at.button:
44
+ if widget.label == label:
45
+ return widget
46
+ raise AssertionError(f"Missing button {label!r}; found {[widget.label for widget in at.button]!r}")
47
+
48
+
49
+ def _has_button(at: AppTest, label: str) -> bool:
50
+ return any(widget.label == label for widget in at.button)
51
+
52
+
53
+ def _has_message(elements, text: str) -> bool:
54
+ return any(text in value for value in _values(elements))
55
+
56
+
57
+ def test_completed_standard_to_expert_edit_blocks_export_until_explicit_recompletion() -> None:
58
+ at = AppTest.from_file(str(APP_PATH)).run(timeout=30)
59
+ _assert_no_exception(at)
60
+ initial_source = _markdown(at, "STATE_SOURCE:")
61
+ initial_generation = _markdown(at, "STATE_GENERATION:")
62
+ assert _has_message(at.success, "EXPORT_AVAILABLE")
63
+ assert "processed=current;reviewed=current;export=current" in _markdown(at, "STATE_LINEAGE:")
64
+
65
+ _radio(at, "Weergave").set_value("Expert")
66
+ at.run(timeout=30)
67
+ _assert_no_exception(at)
68
+ assert _markdown(at, "STATE_SOURCE:") == initial_source
69
+ assert _markdown(at, "STATE_GENERATION:") == initial_generation
70
+ assert "STATE_ANALYSIS_CACHE: `current`" == _markdown(at, "STATE_ANALYSIS_CACHE:")
71
+ assert "STATE_REVIEW_CACHE: `current`" == _markdown(at, "STATE_REVIEW_CACHE:")
72
+ assert _has_message(at.success, "EXPORT_AVAILABLE")
73
+
74
+ _button(at, "Wijzig reviewbeslissing").click()
75
+ at.run(timeout=30)
76
+ _assert_no_exception(at)
77
+ assert _markdown(at, "STATE_SOURCE:") == initial_source
78
+ assert _markdown(at, "STATE_GENERATION:") == initial_generation
79
+ assert "[PERSOON_TEST_99]" in _markdown(at, "STATE_REVIEW_ROW:")
80
+ assert "processed=current;reviewed=stale;export=stale" in _markdown(at, "STATE_LINEAGE:")
81
+ assert _has_message(at.info, "EXPORT_BLOCKED")
82
+ assert not _has_message(at.success, "EXPORT_AVAILABLE")
83
+ assert _has_button(at, "Controle opnieuw afronden")
84
+
85
+ _button(at, "Controle opnieuw afronden").click()
86
+ at.run(timeout=30)
87
+ _assert_no_exception(at)
88
+ assert "[PERSOON_TEST_99]" in _markdown(at, "STATE_REVIEW_ROW:")
89
+ assert "processed=current;reviewed=current;export=current" in _markdown(at, "STATE_LINEAGE:")
90
+ assert _has_message(at.success, "EXPORT_AVAILABLE")
91
+
92
+ _radio(at, "Weergave").set_value("Standaard")
93
+ at.run(timeout=30)
94
+ _assert_no_exception(at)
95
+ assert _markdown(at, "STATE_SOURCE:") == initial_source
96
+ assert _markdown(at, "STATE_GENERATION:") == initial_generation
97
+ assert "[PERSOON_TEST_99]" in _markdown(at, "STATE_REVIEW_ROW:")
98
+ assert "STATE_ANALYSIS_CACHE: `current`" == _markdown(at, "STATE_ANALYSIS_CACHE:")
99
+ assert "STATE_REVIEW_CACHE: `current`" == _markdown(at, "STATE_REVIEW_CACHE:")
100
+ assert _has_message(at.success, "EXPORT_AVAILABLE")
101
+
102
+
103
+ def test_real_processing_setting_change_clears_caches_and_blocks_export() -> None:
104
+ at = AppTest.from_file(str(APP_PATH)).run(timeout=30)
105
+ _assert_no_exception(at)
106
+ initial_generation = _markdown(at, "STATE_GENERATION:")
107
+
108
+ _radio(at, "Weergave").set_value("Expert")
109
+ at.run(timeout=30)
110
+ _button(at, "Wijzig verwerkingsinstelling").click()
111
+ at.run(timeout=30)
112
+ _assert_no_exception(at)
113
+
114
+ assert _markdown(at, "STATE_GENERATION:") != initial_generation
115
+ assert "STATE_ANALYSIS_CACHE: `missing`" == _markdown(at, "STATE_ANALYSIS_CACHE:")
116
+ assert "STATE_REVIEW_CACHE: `missing`" == _markdown(at, "STATE_REVIEW_CACHE:")
117
+ assert "processed=stale;reviewed=stale;export=stale" in _markdown(at, "STATE_LINEAGE:")
118
+ assert _has_message(at.info, "EXPORT_BLOCKED")
119
+ assert not _has_button(at, "Controle opnieuw afronden")
120
+
121
+
122
+ def test_expert_only_operator_is_preserved_when_returning_to_standard() -> None:
123
+ at = AppTest.from_file(str(APP_PATH)).run(timeout=30)
124
+ _assert_no_exception(at)
125
+
126
+ _radio(at, "Weergave").set_value("Expert")
127
+ at.run(timeout=30)
128
+ _selectbox(at, "Expert operator").set_value("highlight")
129
+ at.run(timeout=30)
130
+ _assert_no_exception(at)
131
+ assert "STATE_OPERATOR: `highlight`" == _markdown(at, "STATE_OPERATOR:")
132
+
133
+ _radio(at, "Weergave").set_value("Standaard")
134
+ at.run(timeout=30)
135
+ _assert_no_exception(at)
136
+ assert "STATE_OPERATOR: `highlight`" == _markdown(at, "STATE_OPERATOR:")
137
+ assert _has_message(at.warning, "EXPERT_OPERATOR_REQUIRED")
138
+ assert _has_message(at.info, "EXPORT_BLOCKED")
139
+
140
+
141
+ def test_production_export_buttons_are_behind_the_executable_readiness_gate() -> None:
142
+ source = PRODUCTION_APP_PATH.read_text(encoding="utf-8")
143
+ gate_call = source.index("export_surface_ready = render_export_readiness_gate(")
144
+ stop_guard = source.index("if not export_surface_ready:", gate_call)
145
+ first_document_download = source.index('key="download_txt"', stop_guard)
146
+ guarded_section = source[gate_call:first_document_download]
147
+
148
+ assert gate_call < stop_guard < first_document_download
149
+ assert "st.stop()" in guarded_section
150
+ assert "reviewed_rows=edited_replacements_df.to_dict(\"records\")" in guarded_section