From 424332f910ba79735668541adc45e00dab1911c8 Mon Sep 17 00:00:00 2001 From: adeebahmed Date: Mon, 13 Apr 2026 21:23:51 -0500 Subject: [PATCH] feat: add onboarding status endpoint and phase 3 settings tests --- backend/routers/settings.py | 35 ++++++++++++- backend/tests/test_onboarding.py | 86 ++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 backend/tests/test_onboarding.py diff --git a/backend/routers/settings.py b/backend/routers/settings.py index 98e3352..280b70b 100644 --- a/backend/routers/settings.py +++ b/backend/routers/settings.py @@ -4,7 +4,7 @@ import json from ..database import get_db -from ..models import Setting +from ..models import Account, Setting router = APIRouter(prefix="/api/settings", tags=["settings"]) @@ -19,6 +19,19 @@ class SettingValue(BaseModel): "claude_api_key", "watch_folder_path", "projection_return_rates", + "income_w2", + "income_1099", + "tax_filing_status", + "birth_year", + "retirement_age", + "monthly_contribution", + "retirement_target_amount", + "fire_type", + "monthly_income", + "annual_lean_expenses", + "annual_fat_expenses", + "part_time_income", + "onboarding_complete", } @@ -64,3 +77,23 @@ def delete_setting(key: str, db: Session = Depends(get_db)): db.delete(s) db.commit() return {"ok": True} + + +@router.get("/onboarding/status") +def onboarding_status(db: Session = Depends(get_db)): + account_count = db.query(Account).count() + onboarding_row = db.query(Setting).get("onboarding_complete") + + onboarding_complete = False + if onboarding_row and onboarding_row.value: + try: + onboarding_complete = bool(json.loads(onboarding_row.value)) + except (json.JSONDecodeError, TypeError): + onboarding_complete = str(onboarding_row.value).strip().lower() == "true" + + return { + "has_accounts": account_count > 0, + "account_count": account_count, + "onboarding_complete": onboarding_complete, + "should_run_onboarding": (account_count == 0) or (not onboarding_complete), + } diff --git a/backend/tests/test_onboarding.py b/backend/tests/test_onboarding.py new file mode 100644 index 0000000..a9e6986 --- /dev/null +++ b/backend/tests/test_onboarding.py @@ -0,0 +1,86 @@ +from fastapi import FastAPI +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import StaticPool + +from backend.database import Base, get_db +from backend.models import Account +from backend.routers.settings import router as settings_router + + +def _make_client(): + engine = create_engine( + "sqlite://", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + Base.metadata.create_all(bind=engine) + session_factory = sessionmaker(bind=engine, autocommit=False, autoflush=False, expire_on_commit=False) + + app = FastAPI() + app.include_router(settings_router) + + def override_get_db(): + session = session_factory() + try: + yield session + finally: + session.close() + + app.dependency_overrides[get_db] = override_get_db + client = TestClient(app) + return client, session_factory + + +def test_onboarding_status_defaults_to_should_run(): + client, _ = _make_client() + res = client.get("/api/settings/onboarding/status") + assert res.status_code == 200 + body = res.json() + assert body["has_accounts"] is False + assert body["account_count"] == 0 + assert body["onboarding_complete"] is False + assert body["should_run_onboarding"] is True + + +def test_onboarding_status_false_when_accounts_and_flag_set(): + client, session_factory = _make_client() + + db = session_factory() + db.add(Account(name="Checking", type="checking")) + db.commit() + db.close() + + put_res = client.put("/api/settings/onboarding_complete", json={"value": True}) + assert put_res.status_code == 200 + + res = client.get("/api/settings/onboarding/status") + assert res.status_code == 200 + body = res.json() + assert body["has_accounts"] is True + assert body["account_count"] == 1 + assert body["onboarding_complete"] is True + assert body["should_run_onboarding"] is False + + +def test_phase3_settings_keys_round_trip(): + client, _ = _make_client() + + payloads = { + "fire_type": "regular", + "monthly_income": 9000, + "annual_lean_expenses": 36000, + "annual_fat_expenses": 120000, + "part_time_income": 18000, + "onboarding_complete": True, + } + for key, value in payloads.items(): + res = client.put(f"/api/settings/{key}", json={"value": value}) + assert res.status_code == 200 + + settings_res = client.get("/api/settings") + assert settings_res.status_code == 200 + settings = settings_res.json() + for key, value in payloads.items(): + assert settings[key] == value