feat: add GET/PUT /api/v1/settings endpoints
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V57jSQPqwkGG8BuAXg59X5
This commit is contained in:
parent
df933d089b
commit
a904f2f8e0
|
|
@ -3,7 +3,7 @@ JARVIS API Backend
|
||||||
FastAPI application for JARVIS AI Assistant + Business Automation
|
FastAPI application for JARVIS AI Assistant + Business Automation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Depends, Header
|
from fastapi import FastAPI, HTTPException, Depends, Header, Body
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -1497,6 +1497,20 @@ async def get_orders(status: Optional[str] = None):
|
||||||
logger.error(f"Orders fetch error: {str(e)}")
|
logger.error(f"Orders fetch error: {str(e)}")
|
||||||
raise HTTPException(status_code=503, detail=f"Orders unavailable: {str(e)}")
|
raise HTTPException(status_code=503, detail=f"Orders unavailable: {str(e)}")
|
||||||
|
|
||||||
|
@app.get("/api/v1/settings", dependencies=[Depends(require_admin_key)])
|
||||||
|
async def get_settings_endpoint():
|
||||||
|
return await get_all_settings()
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/api/v1/settings", dependencies=[Depends(require_admin_key)])
|
||||||
|
async def update_settings_endpoint(updates: dict = Body(...)):
|
||||||
|
for key in updates:
|
||||||
|
if key not in SETTINGS_DEFAULTS:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Unknown setting: {key}")
|
||||||
|
for key, value in updates.items():
|
||||||
|
await set_setting(key, str(value))
|
||||||
|
return await get_all_settings()
|
||||||
|
|
||||||
@app.get("/api/v1/emails", dependencies=[Depends(require_admin_key)])
|
@app.get("/api/v1/emails", dependencies=[Depends(require_admin_key)])
|
||||||
async def get_cached_emails(limit: int = 10):
|
async def get_cached_emails(limit: int = 10):
|
||||||
"""Recently cached emails, populated by the n8n 'Neue E-Mails Cache' workflow"""
|
"""Recently cached emails, populated by the n8n 'Neue E-Mails Cache' workflow"""
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,54 @@ async def test_set_setting_upserts_value():
|
||||||
assert "INSERT INTO settings" in args[0]
|
assert "INSERT INTO settings" in args[0]
|
||||||
assert "ON CONFLICT (key) DO UPDATE" in args[0]
|
assert "ON CONFLICT (key) DO UPDATE" in args[0]
|
||||||
assert args[1] == ("company_name", "Neue Firma GmbH")
|
assert args[1] == ("company_name", "Neue Firma GmbH")
|
||||||
|
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
main.API_KEY_ADMIN = "test-secret"
|
||||||
|
HEADERS = {"X-Admin-Key": "test-secret"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def client():
|
||||||
|
with TestClient(main.app) as c:
|
||||||
|
yield c
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_settings_endpoint_returns_merged_settings(client):
|
||||||
|
settings = {"assistant_name": "JARVIS", "company_name": "MBO-Tech-IT", "contact_email": "kontakt@mbo-tech-it.de"}
|
||||||
|
with patch.object(main, "get_all_settings", new=AsyncMock(return_value=settings)):
|
||||||
|
response = client.get("/api/v1/settings", headers=HEADERS)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == settings
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_settings_endpoint_requires_admin_key(client):
|
||||||
|
response = client.get("/api/v1/settings")
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_put_settings_endpoint_updates_and_returns_merged_settings(client):
|
||||||
|
updated = {"assistant_name": "FRIDAY", "company_name": "MBO-Tech-IT", "contact_email": "kontakt@mbo-tech-it.de"}
|
||||||
|
with patch.object(main, "set_setting", new=AsyncMock()) as mock_set, patch.object(
|
||||||
|
main, "get_all_settings", new=AsyncMock(return_value=updated)
|
||||||
|
):
|
||||||
|
response = client.put("/api/v1/settings", headers=HEADERS, json={"assistant_name": "FRIDAY"})
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == updated
|
||||||
|
mock_set.assert_called_once_with("assistant_name", "FRIDAY")
|
||||||
|
|
||||||
|
|
||||||
|
def test_put_settings_endpoint_rejects_unknown_key(client):
|
||||||
|
with patch.object(main, "set_setting", new=AsyncMock()) as mock_set:
|
||||||
|
response = client.put("/api/v1/settings", headers=HEADERS, json={"nonsense_key": "x"})
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
mock_set.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_put_settings_endpoint_requires_admin_key(client):
|
||||||
|
response = client.put("/api/v1/settings", json={"assistant_name": "X"})
|
||||||
|
assert response.status_code == 401
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue