shopstack / tests /test_app.py
pranaysuyash's picture
Fix PWA route re-mount after Gradio launch
22ef457 verified
Raw
History Blame Contribute Delete
5.84 kB
from __future__ import annotations
import os
from pathlib import Path
import gradio as gr
def test_build_app_returns_blocks(app):
built = app.build_app()
assert isinstance(built, gr.Blocks)
def test_build_app_title(app):
built = app.build_app()
assert built.title == "ShopStack"
def test_today_dashboard_returns_correct_shape(app):
from shopstack.ui.screens import today_dashboard
results = today_dashboard()
assert len(results) == 6
for r in results:
assert isinstance(r, str)
def test_launch_reinstalls_pwa_and_health_routes(monkeypatch, app):
"""The launch wrapper should restore routes after Gradio rebuilds the app."""
import app as app_module
calls: list[str] = []
def fake_mount_pwa_static(blocks):
calls.append("pwa")
def fake_mount_health_endpoint(blocks, db):
calls.append("health")
def fake_launch(self, *args, **kwargs):
calls.append("launch")
return "launched"
monkeypatch.setattr(app_module, "mount_pwa_static", fake_mount_pwa_static)
monkeypatch.setattr(app_module, "mount_health_endpoint", fake_mount_health_endpoint)
monkeypatch.setattr(gr.Blocks, "launch", fake_launch, raising=False)
built = app_module.build_app()
assert calls.count("pwa") == 1
assert calls.count("health") == 1
result = built.launch()
assert result == "launched"
assert calls.count("launch") == 1
assert calls.count("pwa") == 2
assert calls.count("health") == 2
def test_all_view_functions_importable(app):
"""Verify all tab builders are registered in the tab registry."""
from shopstack.ui.tabs.registry import get_builder, registered_tab_ids
expected = {
"today", "basket", "market", "reconcile", "memory",
"cookbook", "scanner", "timeline", "find_trail", "photo_map",
"repair_inbox", "nutrition", "store_mode", "smart_basket",
"analytics", "consumption", "recipe", "parser", "community",
"market_intel", "trip_advisor",
}
registered = registered_tab_ids()
missing = expected - registered
assert not missing, f"Registry missing builders for tabs: {missing}"
for tab_id in expected:
builder = get_builder(tab_id)
assert callable(builder), f"Builder for {tab_id} is not callable"
from shopstack.ui.screens import (
shelf_scan_confirm,
shelf_scan_process,
shelf_scan_save_trace,
shelf_scan_skip,
)
for fn in (
shelf_scan_process,
shelf_scan_confirm,
shelf_scan_skip,
shelf_scan_save_trace,
):
assert callable(fn)
def test_build_app_appears_to_have_tabs(app):
built = app.build_app()
children = list(built.children)
assert len(children) > 0
def test_generate_shopping_poster_e2e(app):
from datetime import date, timedelta
from shopstack.app_context import db
from shopstack.schemas.models import InventoryLot, ShoppingListItem, Store
uid = db.active_household_id or "default_household"
db.active_household_id = uid
assert uid in {h["household_id"] for h in db.list_households()}
db.add_store(Store(
store_id="test_store", name="Test Store",
location="Test", store_type="supermarket",
))
today = date.today()
db.add_inventory_lot(InventoryLot(
lot_id="test_milk", canonical_name="milk",
display_name="Test Milk", quantity=0.5, unit="L",
storage_location_id="fridge",
purchase_date=today - timedelta(days=2),
), user_id=uid)
sl = db.create_shopping_list(
name="Weekly Grocery Run",
goal="Stock up for the week",
user_id=uid,
)
items_data = [
("milk", 2.0, "L", "must_buy", "Only 0.5L left"),
("tomato", 1.0, "kg", "must_buy", "Almost out"),
("bread", 1.0, "loaf", "optional", "Current loaf expires soon"),
("toor_dal", 1.0, "kg", "must_buy", "Running low on dal"),
]
for canonical, qty, unit, priority, reason in items_data:
item = ShoppingListItem(
canonical_name=canonical,
requested_quantity=qty,
unit=unit,
priority=priority,
reason=reason,
)
db.add_list_item(sl.list_id, item)
from shopstack.ui.screens import generate_shopping_poster
poster_path, status_html = generate_shopping_poster()
assert status_html, "Status HTML should not be empty"
if poster_path:
assert os.path.isfile(poster_path), f"Poster file should exist: {poster_path}"
is_svg = poster_path.endswith(".svg")
is_png = poster_path.endswith(".png")
assert is_svg or is_png, f"Expected .svg or .png, got {Path(poster_path).suffix}"
if is_svg:
with open(poster_path, errors="replace") as f:
content = f.read()
assert "milk" in content.lower()
assert "tomato" in content.lower()
assert "bread" in content.lower()
else:
try:
from PIL import Image
img = Image.open(poster_path).convert("RGB")
w, h = img.size
assert w > 0 and h > 0, f"Poster image has invalid dimensions: {w}x{h}"
except ImportError:
pass
assert "saved" in status_html.lower() or "✓" in status_html
os.unlink(poster_path)
try:
Path(poster_path).parent.rmdir()
except OSError:
pass
else:
assert "provider" in status_html.lower() or "available" in status_html.lower()
def test_today_tab_does_not_expose_demo_loader():
app_source = Path(__file__).resolve().parents[1] / "app.py"
text = app_source.read_text()
assert "Load Demo Data" not in text
assert "seed_demo_inventory" not in text