Sweep follow-up to 93e43fc. Display labels now consistent across docs,
landing pages, CLI output, code comments, docstrings, and test prose.
Five parallel surfaces touched:
- docs (EN + ES): README, USER-GUIDE, CLI-REFERENCE, and 11 internal
design/planning docs
- landing pages: index + bookkeeper/revops/shopify-pet
- src: CLI module docstrings, _TOOL_DISPLAY dicts in cli_analyze.py
and gui/components/_legacy.py, core module headers, every tool
page's module docstring
- tests: class/method/module docstrings and section-header comments
- test-cases READMEs
Page slugs (1_Deduplicator etc.), tool_id strings (01_deduplicator
etc.), Python class names (TestDeduplicatorWorkflow, FeatureFlag.*),
URL paths, anchor IDs, CSS classes, and asset filenames were left
intact since they're code identifiers / structural references.
All 2033 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
4.0 KiB
Python
104 lines
4.0 KiB
Python
"""Error-display tests.
|
|
|
|
Tool pages catch core exceptions (via ``format_for_user``) and surface
|
|
them through ``st.error``. We verify that the message structure makes
|
|
it through the GUI layer, not just that it gets raised by core (the
|
|
core tests already cover that).
|
|
|
|
These tests deliberately feed garbage bytes / malformed content and
|
|
check the rendered error, not just that the page didn't crash.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from .conftest import collected_text, stash_upload
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Malformed upload
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestMalformedUploadErrors:
|
|
"""Bytes that look like a CSV but aren't parseable. The Find Duplicates
|
|
page wraps ``read_file`` failures in an ``st.error`` with the file
|
|
name and the structured ``format_for_user`` output."""
|
|
|
|
@pytest.fixture
|
|
def garbage_bytes(self) -> bytes:
|
|
"""Binary garbage with embedded NULs and non-UTF-8 sequences —
|
|
triggers the gate's repair pipeline failures, ultimately
|
|
produces a parse error on the dedup page if it makes it that
|
|
far. We bypass the gate so the dedup page sees it raw."""
|
|
return b"\xff\xfe\x00\x01\x02garbage,without,structure\n\x00\xff" * 50
|
|
|
|
def test_garbage_bytes_do_not_crash_dedup(
|
|
self, app_factory, garbage_bytes,
|
|
):
|
|
app = app_factory("1_Deduplicator")
|
|
stash_upload(app, name="garbage.csv", data=garbage_bytes)
|
|
app.run()
|
|
# The page should either render an error OR successfully parse
|
|
# the bytes as text (the gate has been pre-passed, so the
|
|
# pre-parse repair didn't run on this fixture). We just need
|
|
# no uncaught Python exception.
|
|
assert not app.exception
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Empty upload
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestEmptyUpload:
|
|
"""Zero-byte upload — must be handled gracefully."""
|
|
|
|
def test_empty_bytes_renders(self, app_factory):
|
|
app = app_factory("1_Deduplicator")
|
|
stash_upload(app, name="empty.csv", data=b"")
|
|
app.run()
|
|
# Either: (a) we render an error, or (b) we render the page
|
|
# with no preview. Either is acceptable — what's NOT is an
|
|
# uncaught Python exception bubbling up.
|
|
assert not app.exception
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Single-column file
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestSingleColumnFile:
|
|
"""A 1-column CSV is technically valid but produces no auto-detect
|
|
strategies. The page must explain this to the user rather than
|
|
silently producing zero match groups."""
|
|
|
|
def test_single_column_does_not_crash(self, app_factory):
|
|
app = app_factory("1_Deduplicator")
|
|
data = b"only_col\nvalue1\nvalue2\nvalue3\n"
|
|
stash_upload(app, name="single.csv", data=data)
|
|
app.run()
|
|
assert not app.exception
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Header collision in column_mapper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestColumnMapperDuplicateTarget:
|
|
"""The column mapper rejects mappings where two source columns
|
|
point at the same target. This is surfaced as an error.
|
|
|
|
Test approach: ``map_columns`` validates upfront via core, and
|
|
raises ``InputValidationError`` — the GUI wraps it. We invoke the
|
|
core function directly to pin the validation contract."""
|
|
|
|
def test_duplicate_target_raises(self):
|
|
import pandas as pd
|
|
from src.core.column_mapper import map_columns, MapOptions
|
|
from src.core.errors import InputValidationError
|
|
|
|
df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
|
|
opts = MapOptions(mapping={"a": "name", "b": "name"})
|
|
with pytest.raises(InputValidationError):
|
|
map_columns(df, opts)
|