Files
datatools-dev/tests/gui/test_errors.py
Michael db5ec084da docs+code: rename tool labels everywhere
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>
2026-05-16 19:50:09 +00:00

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)