|
@@ -1,8 +1,9 @@
|
|
|
import unittest
|
|
import unittest
|
|
|
-from datetime import date
|
|
|
|
|
|
|
+from datetime import date, datetime
|
|
|
from types import SimpleNamespace
|
|
from types import SimpleNamespace
|
|
|
from unittest.mock import MagicMock, patch
|
|
from unittest.mock import MagicMock, patch
|
|
|
from urllib.parse import parse_qs, urlencode, urlsplit
|
|
from urllib.parse import parse_qs, urlencode, urlsplit
|
|
|
|
|
+from zoneinfo import ZoneInfo
|
|
|
|
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi import FastAPI
|
|
|
from fastapi.testclient import TestClient
|
|
from fastapi.testclient import TestClient
|
|
@@ -249,6 +250,7 @@ class DetailReturnTests(unittest.TestCase):
|
|
|
super().__init__()
|
|
super().__init__()
|
|
|
self.values = {}
|
|
self.values = {}
|
|
|
self.save_back = None
|
|
self.save_back = None
|
|
|
|
|
+ self.textarea = None
|
|
|
|
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
def handle_starttag(self, tag, attrs):
|
|
|
attrs = dict(attrs)
|
|
attrs = dict(attrs)
|
|
@@ -261,12 +263,22 @@ class DetailReturnTests(unittest.TestCase):
|
|
|
self.values[attrs["name"]] = attrs.get("value", "on")
|
|
self.values[attrs["name"]] = attrs.get("value", "on")
|
|
|
if tag == "button" and "hx-vals" in attrs:
|
|
if tag == "button" and "hx-vals" in attrs:
|
|
|
self.save_back = attrs
|
|
self.save_back = attrs
|
|
|
|
|
+ if tag == "textarea" and attrs.get("name"):
|
|
|
|
|
+ self.textarea = attrs["name"]
|
|
|
|
|
+ self.values[self.textarea] = ''
|
|
|
|
|
+
|
|
|
|
|
+ def handle_data(self, data):
|
|
|
|
|
+ if self.textarea:
|
|
|
|
|
+ self.values[self.textarea] += data
|
|
|
|
|
|
|
|
def handle_endtag(self, tag):
|
|
def handle_endtag(self, tag):
|
|
|
|
|
+ if tag == "textarea":
|
|
|
|
|
+ self.textarea = None
|
|
|
if tag == "form":
|
|
if tag == "form":
|
|
|
self.active = False
|
|
self.active = False
|
|
|
|
|
|
|
|
self.row['Versicherung_Schadennummer'] = 'SCH-123,45'
|
|
self.row['Versicherung_Schadennummer'] = 'SCH-123,45'
|
|
|
|
|
+ self.row['Schaden_Details'] = 'Stoßfänger beschädigt <vorne> & rechts.\nLackierung nötig, 2 Teile.'
|
|
|
response = self.client.get('/app/forderungen/details/1_R1', params={'return_to': self.return_url})
|
|
response = self.client.get('/app/forderungen/details/1_R1', params={'return_to': self.return_url})
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
form = DetailForm()
|
|
form = DetailForm()
|
|
@@ -289,8 +301,39 @@ class DetailReturnTests(unittest.TestCase):
|
|
|
self.assertEqual(response.headers['HX-Redirect'], self.return_url)
|
|
self.assertEqual(response.headers['HX-Redirect'], self.return_url)
|
|
|
update = next(call for call in self.db.execute.call_args_list if str(call.args[0]).lstrip().startswith('UPDATE'))
|
|
update = next(call for call in self.db.execute.call_args_list if str(call.args[0]).lstrip().startswith('UPDATE'))
|
|
|
self.assertEqual(update.args[1]['Schadennummer'], 'SCH-123,45')
|
|
self.assertEqual(update.args[1]['Schadennummer'], 'SCH-123,45')
|
|
|
|
|
+ self.assertEqual(update.args[1]['Schaden_Details'], self.row['Schaden_Details'])
|
|
|
|
|
+ self.assertIn('[Schaden_Details] = :Schaden_Details', str(update.args[0]))
|
|
|
self.assertEqual(update.args[1]['Wiedervorlage'], date(2026, 11, 18))
|
|
self.assertEqual(update.args[1]['Wiedervorlage'], date(2026, 11, 18))
|
|
|
|
|
|
|
|
|
|
+ def test_form_saves_stamp_authenticated_user_and_server_time(self):
|
|
|
|
|
+ for redirect in ('', 'list'):
|
|
|
|
|
+ with self.subTest(redirect=redirect):
|
|
|
|
|
+ self.db.reset_mock()
|
|
|
|
|
+ before = datetime.now(ZoneInfo('Europe/Berlin')).replace(tzinfo=None)
|
|
|
|
|
+ response = self.client.post('/app/forderungen/details/1_R1', data={
|
|
|
|
|
+ 'source': 'details', 'redirect': redirect, 'return_to': self.return_url,
|
|
|
|
|
+ 'Schaden_Details': 'Neue Schadenbeschreibung',
|
|
|
|
|
+ 'Bearbeitung_Benutzer_ID': 'forged-user',
|
|
|
|
|
+ 'Bearbeitung_Timestamp': '2000-01-01T00:00:00',
|
|
|
|
|
+ }, headers={'HX-Request': 'true'})
|
|
|
|
|
+ after = datetime.now(ZoneInfo('Europe/Berlin')).replace(tzinfo=None)
|
|
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
|
|
+ update = next(call for call in self.db.execute.call_args_list
|
|
|
|
|
+ if str(call.args[0]).lstrip().startswith('UPDATE'))
|
|
|
|
|
+ statement, values = update.args
|
|
|
|
|
+ statement.compile().construct_params(values)
|
|
|
|
|
+ self.assertIn('[Bearbeitung_Benutzer_ID] = :Bearbeitung_Benutzer_ID', str(statement))
|
|
|
|
|
+ self.assertIn('[Bearbeitung_Timestamp] = :Bearbeitung_Timestamp', str(statement))
|
|
|
|
|
+ self.assertEqual(values['Bearbeitung_Benutzer_ID'], 'tester')
|
|
|
|
|
+ self.assertEqual(values['Schaden_Details'], 'Neue Schadenbeschreibung')
|
|
|
|
|
+ self.assertIs(type(values['Bearbeitung_Timestamp']), datetime)
|
|
|
|
|
+ self.assertIsNone(values['Bearbeitung_Timestamp'].tzinfo)
|
|
|
|
|
+ self.assertLessEqual(before, values['Bearbeitung_Timestamp'])
|
|
|
|
|
+ self.assertLessEqual(values['Bearbeitung_Timestamp'], after)
|
|
|
|
|
+ self.db.commit.assert_called_once()
|
|
|
|
|
+ if redirect:
|
|
|
|
|
+ self.assertEqual(response.headers['HX-Redirect'], self.return_url)
|
|
|
|
|
+
|
|
|
def test_failed_save_does_not_return_and_external_target_is_ignored(self):
|
|
def test_failed_save_does_not_return_and_external_target_is_ignored(self):
|
|
|
self.db.commit.side_effect = RuntimeError("test database unavailable")
|
|
self.db.commit.side_effect = RuntimeError("test database unavailable")
|
|
|
response = self.client.post(
|
|
response = self.client.post(
|