Files
aza/AzA march 2026/_test_doku_prompt_ratings.py

251 lines
9.1 KiB
Python
Raw Normal View History

2026-06-13 22:47:31 +02:00
# -*- coding: utf-8 -*-
"""Tests: Bewertungen oeffentlicher Doku-Vorlagen + Update-Badge-Ergaenzungen."""
from __future__ import annotations
import inspect
import os
import sqlite3
import tempfile
import unittest
class TestRatingSchemaAndValues(unittest.TestCase):
def test_schema_idempotent(self):
import aza_doku_prompt_sync as sync
td = tempfile.mkdtemp()
try:
db = os.path.join(td, "t.db")
con = sqlite3.connect(db)
sync.ensure_published_doku_prompts_schema(con)
sync.ensure_published_doku_prompt_ratings_schema(con)
sync.ensure_published_doku_prompt_ratings_schema(con)
tables = {
r[0]
for r in con.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
).fetchall()
}
con.close()
self.assertIn("published_doku_prompt_ratings", tables)
finally:
import shutil
shutil.rmtree(td, ignore_errors=True)
def test_valid_half_steps(self):
from aza_doku_prompt_sync import VALID_HALF_STAR_RATINGS, normalize_rating_value
for v in VALID_HALF_STAR_RATINGS:
self.assertEqual(normalize_rating_value(v), v)
for bad in (0, 5.5, 4.3, -1, None, "x"):
self.assertIsNone(normalize_rating_value(bad))
class TestRatingBackendLogic(unittest.TestCase):
def setUp(self):
self._tmpdir = tempfile.mkdtemp()
self._db = os.path.join(self._tmpdir, "test.db")
import aza_doku_prompt_sync as sync
self.sync = sync
con = sqlite3.connect(self._db)
sync.ensure_published_doku_prompts_schema(con)
now = sync._now_ts()
con.execute(
"""
INSERT INTO published_doku_prompts
(id, user_id, practice_id, doc_type, title, description, content,
author_display, revision, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
"pub_a", "author_u", "p1", "brief", "Titel A", "", "Prompt A",
"Dr. A", 1, "published", now, now,
),
)
con.execute(
"""
INSERT INTO published_doku_prompts
(id, user_id, practice_id, doc_type, title, description, content,
author_display, revision, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
"pub_b", "author_u", "p1", "verlauf", "Titel B", "", "Prompt B",
"Dr. A", 1, "unpublished", now, now,
),
)
con.commit()
con.close()
def tearDown(self):
import shutil
import gc
gc.collect()
shutil.rmtree(self._tmpdir, ignore_errors=True)
def _rate(self, pub_id, user, val):
con = sqlite3.connect(self._db)
try:
return self.sync.upsert_public_prompt_rating(
con, public_prompt_id=pub_id, rater_user_id=user, rating_value=val,
)
finally:
con.close()
def test_foreign_rate_and_average(self):
r1 = self._rate("pub_a", "user_b", 0.5)
self.assertEqual(r1["my_rating"], 0.5)
self.assertEqual(r1["rating_count"], 1)
self.assertEqual(r1["rating_average"], 0.5)
r2 = self._rate("pub_a", "user_c", 5.0)
self.assertEqual(r2["rating_count"], 2)
self.assertEqual(r2["rating_average"], 2.8)
def test_own_template_rejected(self):
con = sqlite3.connect(self._db)
with self.assertRaises(PermissionError):
self.sync.upsert_public_prompt_rating(
con, public_prompt_id="pub_a", rater_user_id="author_u", rating_value=4.0,
)
con.close()
def test_unknown_template(self):
con = sqlite3.connect(self._db)
with self.assertRaises(LookupError):
self.sync.upsert_public_prompt_rating(
con, public_prompt_id="missing", rater_user_id="user_x", rating_value=3.0,
)
con.close()
def test_unpublished_rejected(self):
con = sqlite3.connect(self._db)
with self.assertRaises(PermissionError):
self.sync.upsert_public_prompt_rating(
con, public_prompt_id="pub_b", rater_user_id="user_x", rating_value=3.0,
)
con.close()
def test_update_keeps_count(self):
self._rate("pub_a", "user_b", 3.5)
r = self._rate("pub_a", "user_b", 4.5)
self.assertEqual(r["rating_count"], 1)
self.assertEqual(r["my_rating"], 4.5)
self.assertEqual(r["rating_average"], 4.5)
def test_batch_aggregates_no_n_plus_one(self):
self._rate("pub_a", "user_b", 4.0)
self._rate("pub_a", "user_c", 2.0)
con = sqlite3.connect(self._db)
agg = self.sync.fetch_rating_aggregates_batch(con, ["pub_a"], "user_b")
con.close()
self.assertEqual(agg["pub_a"]["rating_count"], 2)
self.assertEqual(agg["pub_a"]["my_rating"], 4.0)
self.assertEqual(agg["pub_a"]["rating_average"], 3.0)
def test_can_rate_flags(self):
item = {"author_user_id": "author_u", "status": "published"}
self.assertFalse(
self.sync.compute_can_rate(
author_user_id="author_u", current_user_id="author_u", published=True,
)
)
self.assertTrue(
self.sync.compute_can_rate(
author_user_id="author_u", current_user_id="user_x", published=True,
)
)
self.assertFalse(
self.sync.compute_can_rate(
author_user_id="author_u", current_user_id="user_x", published=False,
)
)
class TestRatingClientUI(unittest.TestCase):
def test_normalize_includes_rating_fields(self):
from aza_doku_prompt_sync import normalize_public_doku_item
item = normalize_public_doku_item({
"id": "x",
"user_id": "u1",
"doc_type": "brief",
"title": "T",
"content": "C",
"rating_average": 4.5,
"rating_count": 2,
"my_rating": 4.5,
"can_rate": True,
})
self.assertEqual(item["rating_average"], 4.5)
self.assertEqual(item["rating_count"], 2)
self.assertTrue(item["can_rate"])
def test_star_formatting(self):
import aza_doku_vorlagen as dv
self.assertIn("", dv.render_star_unicode(4.5))
self.assertIn("½", dv.render_star_unicode(4.5))
self.assertEqual(dv.format_rating_count_label(1), "1 Bewertung")
self.assertEqual(dv.format_rating_count_label(2), "2 Bewertungen")
self.assertIn("Noch keine", dv.format_public_rating_summary(None, 0))
def test_public_window_has_rating_column(self):
path = os.path.join(os.path.dirname(__file__), "aza_doku_vorlagen.py")
with open(path, encoding="utf-8") as fh:
src = fh.read()
block = src.split("def open_public_doku_vorlagen_window", 1)[-1].split("\ndef ", 1)[0]
self.assertIn('"rating"', block)
self.assertIn("build_half_star_rating_bar", block)
self.assertIn("Die eigene Vorlage kann nicht bewertet werden", block)
def test_rate_route_registered(self):
src = inspect.getsource(__import__("aza_doku_prompt_sync").register_doku_prompt_routes)
self.assertIn("/v1/doku-prompts/rate/{pub_id}", src)
self.assertIn("fetch_rating_aggregates_batch", src)
class TestUpdateBadgeExtended(unittest.TestCase):
def test_basis14_no_startup_prompt_update(self):
import basis14
src = inspect.getsource(basis14.KGDesktopApp)
self.assertNotIn("prompt_update_if_available", src)
def test_office_shell_optional_only_badge(self):
import aza_office_shell_v1 as shell
src = inspect.getsource(shell._OfficeShellV12._poll_update_button_visibility)
self.assertIn("maybe_show_startup_update_dialog", src)
self.assertIn("below_min_required", src)
def test_chat_host_required_only(self):
src = inspect.getsource(__import__("aza_chat_desktop_host").ChatDesktopHost._bootstrap_chat_host)
self.assertIn("prompt_chat_update_if_required", src)
self.assertNotIn("prompt_update_if_available", src)
def test_incoming_popup_no_update_badge(self):
src = open(
os.path.join(os.path.dirname(__file__), "aza_empfang_incoming_popup.py"),
encoding="utf-8",
).read()
self.assertNotIn("update-badge", src.lower())
self.assertNotIn("Aktuell", src)
def test_chat_badge_not_aktuell(self):
import aza_empfang_webview as ew
src = inspect.getsource(ew.EmpfangWebviewApi._inject_kontakt_update_badge)
self.assertIn("Update", src)
self.assertNotIn("Aktuell", src)
def test_update_install_guard(self):
import desktop_update_check as duc
self.assertTrue(hasattr(duc, "_chat_update_install_inflight"))
self.assertTrue(hasattr(duc, "_update_dialog_open"))
if __name__ == "__main__":
unittest.main()