ea9da34899
PUT /api/settings/ non validava nulla: un csv_delimiter vuoto o di piu' caratteri arrivava intatto nel DB e faceva fallire csv.writer con TypeError dentro close_run (500, run mai chiuso). Aggiunta validazione per chiave nota (csv_delimiter 1 carattere fra , ; TAB |, csv_decimal_separator ',' o '.', auto_logout_minutes vuoto o intero 1-480, company_name/company_logo_path non vuoti) -> 422 sui valori fuori dominio. Chiavi non note passano senza validazione (store generico). Difesa in profondita' in production_export_service._csv_format: un csv_delimiter gia' sporco nel DB (scritto prima di questa validazione) non fa piu' fallire close_run, usa ';' e logga un WARNING. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
185 lines
5.7 KiB
Python
185 lines
5.7 KiB
Python
"""Tests for settings router (/api/settings)."""
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.backend.models.orm.setting import SystemSetting
|
|
from src.backend.models.orm.user import User
|
|
from src.backend.tests.conftest import auth_headers
|
|
|
|
|
|
class TestGetSettings:
|
|
"""GET /api/settings/ tests."""
|
|
|
|
async def test_get_settings(
|
|
self,
|
|
client: AsyncClient,
|
|
admin_user: User,
|
|
db_session: AsyncSession,
|
|
):
|
|
"""Authenticated user can retrieve settings."""
|
|
# Seed a setting
|
|
setting = SystemSetting(
|
|
setting_key="test_setting",
|
|
setting_value="test_value",
|
|
setting_type="string",
|
|
)
|
|
db_session.add(setting)
|
|
await db_session.flush()
|
|
|
|
resp = await client.get(
|
|
"/api/settings/", headers=auth_headers(admin_user)
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict)
|
|
assert data.get("test_setting") == "test_value"
|
|
|
|
async def test_get_settings_empty(
|
|
self, client: AsyncClient, maker_user: User
|
|
):
|
|
"""Empty settings returns empty dict."""
|
|
resp = await client.get(
|
|
"/api/settings/", headers=auth_headers(maker_user)
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json() == {} or isinstance(resp.json(), dict)
|
|
|
|
async def test_get_setting_by_key(
|
|
self,
|
|
client: AsyncClient,
|
|
admin_user: User,
|
|
db_session: AsyncSession,
|
|
):
|
|
"""Settings dict contains specific key after creation."""
|
|
setting = SystemSetting(
|
|
setting_key="csv_delimiter",
|
|
setting_value=";",
|
|
setting_type="string",
|
|
)
|
|
db_session.add(setting)
|
|
await db_session.flush()
|
|
|
|
resp = await client.get(
|
|
"/api/settings/", headers=auth_headers(admin_user)
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json()["csv_delimiter"] == ";"
|
|
|
|
|
|
class TestUpdateSettings:
|
|
"""PUT /api/settings/ tests."""
|
|
|
|
async def test_update_settings(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
"""Admin can update/create settings."""
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={
|
|
"company_name": "Tielogic Srl",
|
|
"csv_delimiter": ",",
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["message"].startswith("Updated")
|
|
assert "company_name" in data["updated_keys"]
|
|
assert "csv_delimiter" in data["updated_keys"]
|
|
|
|
# Verify persistence
|
|
get_resp = await client.get(
|
|
"/api/settings/", headers=auth_headers(admin_user)
|
|
)
|
|
settings = get_resp.json()
|
|
assert settings["company_name"] == "Tielogic Srl"
|
|
|
|
async def test_settings_require_admin(
|
|
self, client: AsyncClient, maker_user: User
|
|
):
|
|
"""Non-admin user cannot update settings."""
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(maker_user),
|
|
json={"some_key": "some_value"},
|
|
)
|
|
assert resp.status_code == 403
|
|
|
|
|
|
class TestUpdateSettingsValidation:
|
|
"""gitea#23: an invalid csv_delimiter must be rejected, not accepted and
|
|
left to blow up close_run later with a csv.writer TypeError."""
|
|
|
|
async def test_csv_delimiter_two_chars_rejected(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={"csv_delimiter": ";;"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
async def test_csv_delimiter_empty_rejected(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={"csv_delimiter": ""},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
async def test_csv_delimiter_tab_accepted(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={"csv_delimiter": "\t"},
|
|
)
|
|
assert resp.status_code == 200
|
|
|
|
async def test_csv_decimal_separator_invalid_rejected(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={"csv_decimal_separator": "x"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
async def test_auto_logout_minutes_out_of_range_rejected(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={"auto_logout_minutes": "0"},
|
|
)
|
|
assert resp.status_code == 422
|
|
|
|
async def test_auto_logout_minutes_empty_disables(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
"""Empty string is what the settings page sends to disable auto-logout."""
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={"auto_logout_minutes": ""},
|
|
)
|
|
assert resp.status_code == 200
|
|
|
|
async def test_unknown_key_not_validated(
|
|
self, client: AsyncClient, admin_user: User
|
|
):
|
|
"""Keys with no known validator pass through (generic settings store)."""
|
|
resp = await client.put(
|
|
"/api/settings/",
|
|
headers=auth_headers(admin_user),
|
|
json={"some_future_key": "anything"},
|
|
)
|
|
assert resp.status_code == 200
|