"""GET /home — state-aware landing page.
The boolean ``users.onboarded`` drives template selection. No
auto-transition: the not-onboarded view stays put until the user reloads
(the brainstorm called this out explicitly — quiet UI is preferable to a
surprise redirect mid-setup).
See origin: docs/brainstorms/home-page-requirements.md.
"""
from __future__ import annotations
import tempfile
import uuid
import pytest
@pytest.fixture
def fresh_db(monkeypatch):
with tempfile.TemporaryDirectory() as tmp:
monkeypatch.setenv("DATA_DIR", tmp)
monkeypatch.setenv("TESTING", "1")
monkeypatch.setenv("JWT_SECRET_KEY", "test-jwt-secret-key-minimum-32-chars!!")
yield tmp
def _make_user_and_session(conn, email="u@example.com", onboarded=False):
from src.repositories.users import UserRepository
from app.auth.jwt import create_access_token
uid = str(uuid.uuid4())
UserRepository(conn).create(id=uid, email=email, name=email.split("@")[0])
if onboarded:
conn.execute("UPDATE users SET onboarded = TRUE WHERE id = ?", [uid])
return uid, create_access_token(user_id=uid, email=email)
def _client(follow_redirects: bool = True):
from fastapi.testclient import TestClient
from app.main import app
return TestClient(app, follow_redirects=follow_redirects)
def test_home_unauth_redirects_to_login(fresh_db):
"""Non-API HTML routes redirect 401→/login per app.main's
StarletteHTTPException handler. /home follows that contract."""
c = _client(follow_redirects=False)
resp = c.get("/home")
assert resp.status_code == 302
assert resp.headers["location"].startswith("/login")
def test_home_not_onboarded_user_sees_setup_view(fresh_db):
"""A FALSE-onboarded user gets the install/setup template, identifiable
by its 'Install Claude Code' heading and the self-mark button."""
from src.db import get_system_db, close_system_db
conn = get_system_db()
try:
_, sess = _make_user_and_session(conn, onboarded=False)
finally:
conn.close()
close_system_db()
c = _client()
resp = c.get("/home", cookies={"access_token": sess})
assert resp.status_code == 200
body = resp.text
assert "install Claude Code" in body # step 1 label
assert "install Agnes" in body # step 2 label
assert "self-mark-btn" in body # self-acknowledged escape hatch
assert "setupClaudeBtn" in body # primary one-click CTA from shared partial
def test_home_onboarded_user_sees_nav_hub(fresh_db):
"""A TRUE-onboarded user gets the post-onboarding view: the blue
install-hero is gone entirely (no welcome banner, no completion
badge, no inline step commands), the offboard escape strip is the
only setup-flow remnant rendered, and the rest of /home (connector
tiles, news, etc.) stays. PR #289 collapsed the dual-state hero
into a single not-onboarded-only render — pre-PR the onboarded
branch reused the same `.install-hero` shell with welcome copy
and a "Steps 1–4 done" badge."""
from src.db import get_system_db, close_system_db
conn = get_system_db()
try:
_, sess = _make_user_and_session(conn, onboarded=True)
finally:
conn.close()
close_system_db()
c = _client()
resp = c.get("/home", cookies={"access_token": sess})
assert resp.status_code == 200
body = resp.text
# Install hero entirely absent for onboarded users.
assert '
' not in body
# Offboard escape strip + its button replace the in-hero self-mark control.
assert '
' in body
assert "Mark me as offboarded" in body
# All six inline install-blocks are hidden post-onboarding — the
# labels rendered inside the install-block divs go away. Labels
# tracked with the CEO-mock-parity 6-step rename (Step 3 inserted
# as "Open a terminal", Step 6 added as the optional shortcut
# alias; the old Step 3 became Step 4).
assert "Step 1 — Install Claude Code on your computer" not in body
assert "Step 2 — Pick a folder for" not in body
assert "Step 3 — Open a terminal inside that folder" not in body
assert "Step 4 — Launch Claude with auto-approve on" not in body
assert "Step 5 — Get the install script" not in body
assert "Step 6 — Optional: create a one-word shortcut" not in body
def test_connectors_section_removed_from_home(fresh_db):
"""The dedicated `` block was
dropped from `/home` — the install-hero's Step 4 clipboard payload
(rendered via `_claude_setup_instructions.jinja` inside the manual
fallback) already inlines the same Asana / GWS / Atlassian prompts
from `app/web/connector_prompts.py` via
`app/web/setup_instructions.py::_connectors_block`. Showing them
twice on the same page was duplicate UX. The lead paragraph in the
install-hero now mentions the connectors briefly so users still see
the benefit before they hit the install.
Co-asserts the auto-mode block removal that this test originally
pinned — onboarded users still see neither the connectors block
nor the legacy auto-mode peer section."""
from src.db import get_system_db, close_system_db
conn = get_system_db()
try:
_, sess = _make_user_and_session(conn, onboarded=True)
finally:
conn.close()
close_system_db()
c = _client()
resp = c.get("/home", cookies={"access_token": sess})
assert resp.status_code == 200
body = resp.text
# Auto-mode peer section still gone (legacy guard, not regressed).
assert 'class="automode-card"' not in body
assert 'data-section="step3"' not in body
assert "Step 4 — Launch Claude with auto-approve on" not in body
# Dedicated connectors block is gone from /home in BOTH states.
assert 'class="connector-tiles"' not in body
assert 'data-section="connectors"' not in body
# Server-rendered HTML never carries the data-setup-minimized
# attribute on the .home-mock root — that's a client-side
# localStorage decision applied via JS on load.
assert '
' in body
# Not-onboarded path: same — the section disappears regardless of
# state. Lead-paragraph still surfaces the connector names so users
# know the benefit exists before they kick off the install.
conn = get_system_db()
try:
_, sess2 = _make_user_and_session(
conn, email="not-onboarded@example.com", onboarded=False
)
finally:
conn.close()
close_system_db()
body2 = _client().get("/home", cookies={"access_token": sess2}).text
assert 'class="connector-tiles"' not in body2
assert 'data-section="connectors"' not in body2
# Lead-paragraph mentions the three connector families so the
# benefit isn't lost when the dedicated section disappears.
assert "Asana, Google Workspace, Atlassian" in body2
def test_minimize_toggle_no_longer_rendered(fresh_db):
"""The "Minimize setup view" toggle used to live inside the
onboarded-branch of the install-hero. PR #289 hides the hero
entirely once `users.onboarded=true`, so the minimize toggle
has no rendering site anymore — verify the markup is absent
from both states. (The localStorage `agnes_home_setup_minimized`
flag and its applyMinimize() JS handler stay in the page so a
stale flag from a pre-PR session no-ops cleanly.)"""
from src.db import get_system_db, close_system_db
for onboarded in (False, True):
conn = get_system_db()
try:
_, sess = _make_user_and_session(
conn, email=f"user-{onboarded}@example.com", onboarded=onboarded
)
finally:
conn.close()
close_system_db()
c = _client()
resp = c.get("/home", cookies={"access_token": sess})
assert resp.status_code == 200
assert '