test_forderung_notice.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import unittest
  2. from datetime import date
  3. from decimal import Decimal
  4. from unittest.mock import MagicMock, patch
  5. from fastapi import FastAPI, Request
  6. from fastapi.responses import HTMLResponse
  7. from fastapi.testclient import TestClient
  8. from app.db import get_session
  9. from app.permissions import Principal
  10. from app.routes import router
  11. class ForderungNoticeTests(unittest.TestCase):
  12. def setUp(self):
  13. self.db = MagicMock()
  14. self.application = FastAPI()
  15. self.application.include_router(router)
  16. self.application.dependency_overrides[get_session] = lambda: self.db
  17. @self.application.middleware("http")
  18. async def authenticated(request: Request, call_next):
  19. request.state.principal = Principal("tester", "Tester", "", "Mitarbeiter", "ldap", "csrf", ())
  20. return await call_next(request)
  21. self.client = TestClient(self.application)
  22. self.addCleanup(self.client.close)
  23. self.detail_context_patch = patch("app.routes.get_template_context")
  24. self.detail_context = self.detail_context_patch.start()
  25. self.addCleanup(self.detail_context_patch.stop)
  26. self.current = {
  27. "Beleg_Nr": "WGG24501320",
  28. "Beleg_Datum": date(2024, 5, 13),
  29. "Kunde_Text": "Beispielkunde <script>alert(1)</script>",
  30. "Beleg_Betrag": Decimal("1234.50"),
  31. "Beleg_Offen": Decimal("250.00"),
  32. }
  33. def result(self, row=None, access=False):
  34. result = MagicMock()
  35. result.first.return_value = (1,) if access else None
  36. result.mappings.return_value.first.return_value = row
  37. return result
  38. def get_notice(self, htmx=False):
  39. return self.client.get("/app/forderungen/details/1_WGG24501320", headers={"HX-Request": "true"} if htmx else {})
  40. def assert_notice(self, response):
  41. self.assertEqual(response.status_code, 200)
  42. self.assertIn('href="/app/forderungen/liste?focus=1_WGG24501320">Zurück zur Liste', response.text)
  43. self.assertNotIn('id="formular"', response.text)
  44. self.assertNotIn('name="Kommentar"', response.text)
  45. self.detail_context.assert_not_called()
  46. def test_open_forderung_displays_only_summary_and_skips_history(self):
  47. self.db.execute.side_effect = [self.result(), self.result(self.current)]
  48. response = self.get_notice()
  49. self.assert_notice(response)
  50. self.assertIn("Forderung WGG24501320 vom 13.05.2024 ist noch offen.", response.text)
  51. self.assertIn("1.234,50 €", response.text)
  52. self.assertIn("250,00 €", response.text)
  53. self.assertIn("&lt;script&gt;", response.text)
  54. self.assertNotIn("<script>alert(1)</script>", response.text)
  55. self.assertEqual(self.db.execute.call_count, 2)
  56. def test_history_displays_dates_and_queries_exact_tenant_key(self):
  57. history = {"Beleg_Nr": "WGG24501320", "Beleg_Datum": date(2024, 5, 13), "Auszifferung_Datum": date(2024, 6, 2)}
  58. self.db.execute.side_effect = [self.result(), self.result(), self.result(history)]
  59. response = self.get_notice(htmx=True)
  60. self.assert_notice(response)
  61. self.assertIn("wurde am 02.06.2024 beglichen.", response.text)
  62. query, params = self.db.execute.call_args.args
  63. self.assertIn("dbo.Forderungen_Historie", str(query))
  64. self.assertIn("ORDER BY Auszifferung_Datum DESC", str(query))
  65. self.assertEqual(params, {"client_db": "1", "beleg_nr": "WGG24501320"})
  66. self.assertNotIn("WGG24501320", str(query))
  67. def test_missing_entry_displays_short_message(self):
  68. self.db.execute.side_effect = [self.result(), self.result(), self.result()]
  69. response = self.get_notice()
  70. self.assert_notice(response)
  71. self.assertIn("Eintrag nicht gefunden.", response.text)
  72. def test_null_fields_and_zero_amount_render_correctly(self):
  73. row = dict(self.current, Beleg_Datum=None, Kunde_Text=None, Beleg_Betrag=None, Beleg_Offen=Decimal("0"))
  74. self.db.execute.side_effect = [self.result(), self.result(row)]
  75. response = self.get_notice()
  76. self.assert_notice(response)
  77. self.assertEqual(response.text.count("nicht hinterlegt"), 3)
  78. self.assertIn("0,00 €", response.text)
  79. def test_authorized_user_retains_existing_detail_flow(self):
  80. self.db.execute.return_value = self.result(access=True)
  81. self.detail_context.return_value = {"existing": "context"}
  82. with patch("app.routes.templates.TemplateResponse", return_value=HTMLResponse("Full details")) as render:
  83. response = self.get_notice()
  84. self.assertEqual(response.status_code, 200)
  85. self.assertEqual(response.text, "Full details")
  86. self.detail_context.assert_called_once()
  87. self.assertEqual(render.call_args.args[1], "forderungen/details/details.html")
  88. self.db.execute.assert_called_once()
  89. def test_database_failure_is_not_reported_as_missing(self):
  90. self.db.execute.side_effect = [self.result(), RuntimeError("Database unavailable")]
  91. with TestClient(self.application, raise_server_exceptions=False) as client:
  92. response = client.get("/app/forderungen/details/1_WGG24501320")
  93. self.assertEqual(response.status_code, 500)
  94. self.assertNotIn("Eintrag nicht gefunden", response.text)
  95. def test_write_file_and_export_routes_remain_denied(self):
  96. self.db.execute.return_value = self.result()
  97. cases = [
  98. ("post", "/app/forderungen/details/1_WGG24501320", {"data": {"source": "comments", "Kommentar": "test"}}),
  99. ("get", "/app/forderungen/files/1_WGG24501320", {}),
  100. ("get", "/files/?name=1/WGG24501320/invoice.pdf", {}),
  101. ("get", "/app/forderungen/export/1_WGG24501320/", {}),
  102. ]
  103. for method, url, kwargs in cases:
  104. with self.subTest(url=url):
  105. self.db.execute.reset_mock()
  106. response = getattr(self.client, method)(url, **kwargs)
  107. self.assertEqual(response.status_code, 404)
  108. self.db.execute.assert_called_once()
  109. self.db.commit.assert_not_called()
  110. if __name__ == "__main__":
  111. unittest.main()