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:
Jonny 2026-09-14 07:53:41 +02:00
parent df933d089b
commit a904f2f8e0
2 changed files with 66 additions and 1 deletions

View File

@ -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"""

View File

@ -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