fix(settings): profile tests assert sync-identity; add load/save_user_profile helpers

This commit is contained in:
pyr0ball 2026-03-21 02:31:39 -07:00
parent b8eb2a3890
commit a8b16d616c
3 changed files with 44 additions and 19 deletions

View file

@ -905,6 +905,9 @@ def config_user():
# ── Settings: My Profile endpoints ─────────────────────────────────────────── # ── Settings: My Profile endpoints ───────────────────────────────────────────
from scripts.user_profile import load_user_profile, save_user_profile
def _user_yaml_path() -> str: def _user_yaml_path() -> str:
"""Resolve user.yaml path, falling back to legacy location.""" """Resolve user.yaml path, falling back to legacy location."""
cfg_path = os.path.join(os.path.dirname(DB_PATH), "config", "user.yaml") cfg_path = os.path.join(os.path.dirname(DB_PATH), "config", "user.yaml")
@ -913,22 +916,6 @@ def _user_yaml_path() -> str:
return cfg_path return cfg_path
def _read_user_yaml() -> dict:
import yaml
path = _user_yaml_path()
if not os.path.exists(path):
return {}
with open(path) as f:
return yaml.safe_load(f) or {}
def _write_user_yaml(data: dict) -> None:
import yaml
path = _user_yaml_path()
with open(path, "w") as f:
yaml.dump(data, f, default_flow_style=False, allow_unicode=True)
def _mission_dict_to_list(prefs: object) -> list: def _mission_dict_to_list(prefs: object) -> list:
"""Convert {industry: note} dict to [{industry, note}] list for the SPA.""" """Convert {industry: note} dict to [{industry, note}] list for the SPA."""
if isinstance(prefs, list): if isinstance(prefs, list):
@ -950,7 +937,7 @@ def _mission_list_to_dict(prefs: list) -> dict:
@app.get("/api/settings/profile") @app.get("/api/settings/profile")
def get_profile(): def get_profile():
try: try:
cfg = _read_user_yaml() cfg = load_user_profile(_user_yaml_path())
return { return {
"name": cfg.get("name", ""), "name": cfg.get("name", ""),
"email": cfg.get("email", ""), "email": cfg.get("email", ""),
@ -990,7 +977,8 @@ class UserProfilePayload(BaseModel):
@app.put("/api/settings/profile") @app.put("/api/settings/profile")
def save_profile(payload: UserProfilePayload): def save_profile(payload: UserProfilePayload):
try: try:
cfg = _read_user_yaml() yaml_path = _user_yaml_path()
cfg = load_user_profile(yaml_path)
cfg["name"] = payload.name cfg["name"] = payload.name
cfg["email"] = payload.email cfg["email"] = payload.email
cfg["phone"] = payload.phone cfg["phone"] = payload.phone
@ -1004,7 +992,7 @@ def save_profile(payload: UserProfilePayload):
cfg["nda_companies"] = payload.nda_companies cfg["nda_companies"] = payload.nda_companies
cfg["candidate_accessibility_focus"] = payload.accessibility_focus cfg["candidate_accessibility_focus"] = payload.accessibility_focus
cfg["candidate_lgbtq_focus"] = payload.lgbtq_focus cfg["candidate_lgbtq_focus"] = payload.lgbtq_focus
_write_user_yaml(cfg) save_user_profile(yaml_path, cfg)
return {"ok": True} return {"ok": True}
except Exception as e: except Exception as e:
raise HTTPException(500, f"Could not save profile: {e}") raise HTTPException(500, f"Could not save profile: {e}")

View file

@ -130,3 +130,36 @@ class UserProfile:
"ollama_research": f"{self.ollama_url}/v1", "ollama_research": f"{self.ollama_url}/v1",
"vllm": f"{self.vllm_url}/v1", "vllm": f"{self.vllm_url}/v1",
} }
# ── Free functions for plain-dict access (used by dev-api.py) ─────────────────
def load_user_profile(config_path: str) -> dict:
"""Load user.yaml and return as a plain dict with safe defaults."""
import yaml
from pathlib import Path
path = Path(config_path)
if not path.exists():
return {}
with open(path) as f:
data = yaml.safe_load(f) or {}
return data
def save_user_profile(config_path: str, data: dict) -> None:
"""Atomically write the user profile dict to user.yaml."""
import yaml
import os
import tempfile
from pathlib import Path
path = Path(config_path)
path.parent.mkdir(parents=True, exist_ok=True)
# Write to temp file then rename for atomicity
fd, tmp = tempfile.mkstemp(dir=path.parent, suffix='.yaml.tmp')
try:
with os.fdopen(fd, 'w') as f:
yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
os.replace(tmp, path)
except Exception:
os.unlink(tmp)
raise

View file

@ -29,6 +29,10 @@ describe('useProfileStore', () => {
store.name = 'Meg' store.name = 'Meg'
await store.save() await store.save()
expect(mockFetch).toHaveBeenCalledWith('/api/settings/profile', expect.objectContaining({ method: 'PUT' })) expect(mockFetch).toHaveBeenCalledWith('/api/settings/profile', expect.objectContaining({ method: 'PUT' }))
expect(mockFetch).toHaveBeenCalledWith(
'/api/settings/resume/sync-identity',
expect.objectContaining({ method: 'POST' })
)
}) })
it('save() error sets error state', async () => { it('save() error sets error state', async () => {