fix(settings): valida csv_delimiter/decimal_separator/auto_logout_minutes (#23)

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>
This commit is contained in:
2026-09-21 22:29:35 +02:00
parent 51f3fe32d4
commit ea9da34899
4 changed files with 191 additions and 1 deletions
+77
View File
@@ -105,3 +105,80 @@ class TestUpdateSettings:
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