From a8b16d616ce874d032221d8105ca3ff1634a1453 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sat, 21 Mar 2026 02:31:39 -0700 Subject: [PATCH] fix(settings): profile tests assert sync-identity; add load/save_user_profile helpers --- dev-api.py | 26 ++++++------------- scripts/user_profile.py | 33 +++++++++++++++++++++++++ web/src/stores/settings/profile.test.ts | 4 +++ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/dev-api.py b/dev-api.py index 86c40ca..71a3356 100644 --- a/dev-api.py +++ b/dev-api.py @@ -905,6 +905,9 @@ def config_user(): # ── Settings: My Profile endpoints ─────────────────────────────────────────── +from scripts.user_profile import load_user_profile, save_user_profile + + def _user_yaml_path() -> str: """Resolve user.yaml path, falling back to legacy location.""" 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 -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: """Convert {industry: note} dict to [{industry, note}] list for the SPA.""" if isinstance(prefs, list): @@ -950,7 +937,7 @@ def _mission_list_to_dict(prefs: list) -> dict: @app.get("/api/settings/profile") def get_profile(): try: - cfg = _read_user_yaml() + cfg = load_user_profile(_user_yaml_path()) return { "name": cfg.get("name", ""), "email": cfg.get("email", ""), @@ -990,7 +977,8 @@ class UserProfilePayload(BaseModel): @app.put("/api/settings/profile") def save_profile(payload: UserProfilePayload): try: - cfg = _read_user_yaml() + yaml_path = _user_yaml_path() + cfg = load_user_profile(yaml_path) cfg["name"] = payload.name cfg["email"] = payload.email cfg["phone"] = payload.phone @@ -1004,7 +992,7 @@ def save_profile(payload: UserProfilePayload): cfg["nda_companies"] = payload.nda_companies cfg["candidate_accessibility_focus"] = payload.accessibility_focus cfg["candidate_lgbtq_focus"] = payload.lgbtq_focus - _write_user_yaml(cfg) + save_user_profile(yaml_path, cfg) return {"ok": True} except Exception as e: raise HTTPException(500, f"Could not save profile: {e}") diff --git a/scripts/user_profile.py b/scripts/user_profile.py index fa2678f..4334906 100644 --- a/scripts/user_profile.py +++ b/scripts/user_profile.py @@ -130,3 +130,36 @@ class UserProfile: "ollama_research": f"{self.ollama_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 diff --git a/web/src/stores/settings/profile.test.ts b/web/src/stores/settings/profile.test.ts index fdcb819..a1590c4 100644 --- a/web/src/stores/settings/profile.test.ts +++ b/web/src/stores/settings/profile.test.ts @@ -29,6 +29,10 @@ describe('useProfileStore', () => { store.name = 'Meg' await store.save() 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 () => {