Files
datatools-dev/tests/gui/test_lite_tier.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

136 lines
4.9 KiB
Python

"""GUI tests for the Lite tier.
A Lite license unlocks Find Duplicates, Clean Text, Standardize
Formats. Opening any other tool page (Fix Missing Values, Map
Columns, Automated Workflows, etc.) must render an upgrade prompt
and short-circuit the page body.
The home grid shows a 🔒 Locked badge on the cards for tools the
user's tier doesn't unlock.
"""
from __future__ import annotations
import pytest
from .conftest import collected_text, stash_upload
@pytest.fixture
def lite_license(monkeypatch, tmp_path):
"""Activate a Lite license; return nothing — the env vars route
every page on this test through this license."""
monkeypatch.delenv("DATATOOLS_DEV_MODE", raising=False)
monkeypatch.setenv(
"DATATOOLS_LICENSE_PATH", str(tmp_path / "license.json"),
)
from src.license.manager import reset_singleton_for_tests
reset_singleton_for_tests()
from src.license import LicenseManager, Tier
LicenseManager()._mint(
name="Lite User", email="lite@example.com", tier=Tier.LITE,
)
yield
reset_singleton_for_tests()
# ---------------------------------------------------------------------------
# Unlocked tools render normally
# ---------------------------------------------------------------------------
class TestLiteUnlockedPages:
@pytest.mark.parametrize("slug,signal", [
("1_Deduplicator", "Find Duplicates"),
("2_Text_Cleaner", "Clean Text"),
("3_Format_Standardizer", "Standardize Formats"),
])
def test_unlocked_pages_render_body(
self, lite_license, app_factory, slug, signal, small_csv_bytes,
):
app = app_factory(slug)
stash_upload(app, name="messy.csv", data=small_csv_bytes)
app.run()
text = collected_text(app)
assert signal in text
# No upgrade prompt.
assert "isn't on your" not in text and "no está incluida" not in text
# ---------------------------------------------------------------------------
# Locked tools render upgrade prompt
# ---------------------------------------------------------------------------
class TestLiteLockedPages:
@pytest.mark.parametrize("slug", [
"4_Missing_Values",
"5_Column_Mapper",
"9_Pipeline_Runner",
"6_Outlier_Detector",
"7_Multi_File_Merger",
"8_Validator_Reporter",
])
def test_locked_page_shows_upgrade_prompt(
self, lite_license, app_factory, slug, small_csv_bytes,
):
app = app_factory(slug)
stash_upload(app, name="messy.csv", data=small_csv_bytes)
app.run()
text = collected_text(app)
# Upgrade prompt title carries the localized lock + tier label.
assert "isn't on your" in text or "no está incluida" in text, (
f"locked page {slug!r} missing upgrade prompt; got:\n"
f"{text[:500]}"
)
def test_upgrade_button_present(
self, lite_license, app_factory, small_csv_bytes,
):
app = app_factory("4_Missing_Values")
stash_upload(app, name="messy.csv", data=small_csv_bytes)
app.run()
labels = [b.label for b in app.button]
assert any("Manage license" in lbl for lbl in labels), (
f"upgrade prompt missing Manage-license button; got: {labels}"
)
# ---------------------------------------------------------------------------
# Home grid shows lock badges
# ---------------------------------------------------------------------------
class TestLiteHomeGridBadges:
def test_locked_tool_card_shows_lock_badge(
self, lite_license, home_app,
):
home_app.run()
text = collected_text(home_app)
# Fix Missing Values is locked under Lite — its card should
# have a 🔒 Locked badge.
# We assert the lock glyph appears alongside the locked tool's
# display name. Streamlit renders the markdown verbatim so the
# ``🔒 Locked`` text appears in the page markdown stream.
assert "🔒" in text or "Locked" in text, (
"home grid missing lock badge for Lite-locked tool"
)
def test_unlocked_tool_card_no_lock(self, lite_license, home_app):
home_app.run()
# Dedup is unlocked under Lite. Its card markdown should NOT
# contain a lock glyph adjacent to its name. We can't easily
# scope by card without parsing the markdown stream, but we
# can confirm both ``Ready`` (unlocked) and ``Locked``
# (locked) badges coexist on the page.
text = collected_text(home_app)
assert "Ready" in text or "Listo" in text
# ---------------------------------------------------------------------------
# Sidebar status shows Lite
# ---------------------------------------------------------------------------
class TestLiteSidebarStatus:
def test_sidebar_caption_mentions_lite(self, lite_license, home_app):
home_app.run()
captions = " ".join(c.value for c in home_app.sidebar.caption)
assert "Lite" in captions