fix(settings): profile tests assert sync-identity; add load/save_user_profile helpers
This commit is contained in:
parent
b8eb2a3890
commit
a8b16d616c
3 changed files with 44 additions and 19 deletions
26
dev-api.py
26
dev-api.py
|
|
@ -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}")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue