feat: wizard step validate() functions — all six mandatory steps
This commit is contained in:
parent
537172a4ba
commit
69057f6d10
7 changed files with 184 additions and 0 deletions
14
app/wizard/step_hardware.py
Normal file
14
app/wizard/step_hardware.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""Step 1 — Hardware detection and inference profile selection."""
|
||||
|
||||
PROFILES = ["remote", "cpu", "single-gpu", "dual-gpu"]
|
||||
|
||||
|
||||
def validate(data: dict) -> list[str]:
|
||||
"""Return list of validation errors. Empty list = step passes."""
|
||||
errors = []
|
||||
profile = data.get("inference_profile", "")
|
||||
if not profile:
|
||||
errors.append("Inference profile is required.")
|
||||
elif profile not in PROFILES:
|
||||
errors.append(f"Invalid inference profile '{profile}'. Choose: {', '.join(PROFILES)}.")
|
||||
return errors
|
||||
13
app/wizard/step_identity.py
Normal file
13
app/wizard/step_identity.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"""Step 3 — Identity (name, email, phone, linkedin, career_summary)."""
|
||||
|
||||
|
||||
def validate(data: dict) -> list[str]:
|
||||
"""Return list of validation errors. Empty list = step passes."""
|
||||
errors = []
|
||||
if not (data.get("name") or "").strip():
|
||||
errors.append("Full name is required.")
|
||||
if not (data.get("email") or "").strip():
|
||||
errors.append("Email address is required.")
|
||||
if not (data.get("career_summary") or "").strip():
|
||||
errors.append("Career summary is required.")
|
||||
return errors
|
||||
9
app/wizard/step_inference.py
Normal file
9
app/wizard/step_inference.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"""Step 5 — LLM inference backend configuration and key entry."""
|
||||
|
||||
|
||||
def validate(data: dict) -> list[str]:
|
||||
"""Return list of validation errors. Empty list = step passes."""
|
||||
errors = []
|
||||
if not data.get("endpoint_confirmed"):
|
||||
errors.append("At least one working LLM endpoint must be confirmed.")
|
||||
return errors
|
||||
10
app/wizard/step_resume.py
Normal file
10
app/wizard/step_resume.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"""Step 4 — Resume (upload or guided form builder)."""
|
||||
|
||||
|
||||
def validate(data: dict) -> list[str]:
|
||||
"""Return list of validation errors. Empty list = step passes."""
|
||||
errors = []
|
||||
experience = data.get("experience") or []
|
||||
if not experience:
|
||||
errors.append("At least one work experience entry is required.")
|
||||
return errors
|
||||
13
app/wizard/step_search.py
Normal file
13
app/wizard/step_search.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"""Step 6 — Job search preferences (titles, locations, boards, keywords)."""
|
||||
|
||||
|
||||
def validate(data: dict) -> list[str]:
|
||||
"""Return list of validation errors. Empty list = step passes."""
|
||||
errors = []
|
||||
titles = data.get("job_titles") or []
|
||||
locations = data.get("locations") or []
|
||||
if not titles:
|
||||
errors.append("At least one job title is required.")
|
||||
if not locations:
|
||||
errors.append("At least one location is required.")
|
||||
return errors
|
||||
13
app/wizard/step_tier.py
Normal file
13
app/wizard/step_tier.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"""Step 2 — Tier selection (free / paid / premium)."""
|
||||
from app.wizard.tiers import TIERS
|
||||
|
||||
|
||||
def validate(data: dict) -> list[str]:
|
||||
"""Return list of validation errors. Empty list = step passes."""
|
||||
errors = []
|
||||
tier = data.get("tier", "")
|
||||
if not tier:
|
||||
errors.append("Tier selection is required.")
|
||||
elif tier not in TIERS:
|
||||
errors.append(f"Invalid tier '{tier}'. Choose: {', '.join(TIERS)}.")
|
||||
return errors
|
||||
112
tests/test_wizard_steps.py
Normal file
112
tests/test_wizard_steps.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
# ── Hardware ───────────────────────────────────────────────────────────────────
|
||||
from app.wizard.step_hardware import validate as hw_validate, PROFILES
|
||||
|
||||
def test_hw_valid():
|
||||
assert hw_validate({"inference_profile": "remote"}) == []
|
||||
|
||||
def test_hw_missing():
|
||||
assert hw_validate({}) != []
|
||||
|
||||
def test_hw_invalid():
|
||||
assert hw_validate({"inference_profile": "turbo"}) != []
|
||||
|
||||
def test_hw_all_profiles():
|
||||
for p in PROFILES:
|
||||
assert hw_validate({"inference_profile": p}) == []
|
||||
|
||||
# ── Tier ───────────────────────────────────────────────────────────────────────
|
||||
from app.wizard.step_tier import validate as tier_validate
|
||||
|
||||
def test_tier_valid():
|
||||
assert tier_validate({"tier": "free"}) == []
|
||||
|
||||
def test_tier_missing():
|
||||
assert tier_validate({}) != []
|
||||
|
||||
def test_tier_invalid():
|
||||
assert tier_validate({"tier": "enterprise"}) != []
|
||||
|
||||
# ── Identity ───────────────────────────────────────────────────────────────────
|
||||
from app.wizard.step_identity import validate as id_validate
|
||||
|
||||
def test_id_all_required_fields():
|
||||
d = {"name": "Alice", "email": "a@b.com", "career_summary": "10 years of stuff."}
|
||||
assert id_validate(d) == []
|
||||
|
||||
def test_id_missing_name():
|
||||
d = {"name": "", "email": "a@b.com", "career_summary": "x"}
|
||||
errors = id_validate(d)
|
||||
assert errors != []
|
||||
assert any("name" in e.lower() for e in errors)
|
||||
|
||||
def test_id_missing_email():
|
||||
d = {"name": "Alice", "email": "", "career_summary": "x"}
|
||||
errors = id_validate(d)
|
||||
assert errors != []
|
||||
assert any("email" in e.lower() for e in errors)
|
||||
|
||||
def test_id_missing_summary():
|
||||
d = {"name": "Alice", "email": "a@b.com", "career_summary": ""}
|
||||
errors = id_validate(d)
|
||||
assert errors != []
|
||||
assert any("summary" in e.lower() or "career" in e.lower() for e in errors)
|
||||
|
||||
def test_id_whitespace_only_name():
|
||||
d = {"name": " ", "email": "a@b.com", "career_summary": "x"}
|
||||
assert id_validate(d) != []
|
||||
|
||||
# ── Resume ─────────────────────────────────────────────────────────────────────
|
||||
from app.wizard.step_resume import validate as resume_validate
|
||||
|
||||
def test_resume_no_experience():
|
||||
assert resume_validate({"experience": []}) != []
|
||||
|
||||
def test_resume_one_entry():
|
||||
d = {"experience": [{"company": "Acme", "title": "Engineer", "bullets": ["did stuff"]}]}
|
||||
assert resume_validate(d) == []
|
||||
|
||||
def test_resume_missing_experience_key():
|
||||
assert resume_validate({}) != []
|
||||
|
||||
# ── Inference ──────────────────────────────────────────────────────────────────
|
||||
from app.wizard.step_inference import validate as inf_validate
|
||||
|
||||
def test_inference_not_confirmed():
|
||||
assert inf_validate({"endpoint_confirmed": False}) != []
|
||||
|
||||
def test_inference_confirmed():
|
||||
assert inf_validate({"endpoint_confirmed": True}) == []
|
||||
|
||||
def test_inference_missing():
|
||||
assert inf_validate({}) != []
|
||||
|
||||
# ── Search ─────────────────────────────────────────────────────────────────────
|
||||
from app.wizard.step_search import validate as search_validate
|
||||
|
||||
def test_search_valid():
|
||||
d = {"job_titles": ["Software Engineer"], "locations": ["Remote"]}
|
||||
assert search_validate(d) == []
|
||||
|
||||
def test_search_missing_titles():
|
||||
d = {"job_titles": [], "locations": ["Remote"]}
|
||||
errors = search_validate(d)
|
||||
assert errors != []
|
||||
assert any("title" in e.lower() for e in errors)
|
||||
|
||||
def test_search_missing_locations():
|
||||
d = {"job_titles": ["SWE"], "locations": []}
|
||||
errors = search_validate(d)
|
||||
assert errors != []
|
||||
assert any("location" in e.lower() for e in errors)
|
||||
|
||||
def test_search_missing_both():
|
||||
errors = search_validate({})
|
||||
assert len(errors) == 2
|
||||
|
||||
def test_search_none_values():
|
||||
d = {"job_titles": None, "locations": None}
|
||||
assert search_validate(d) != []
|
||||
Loading…
Reference in a new issue