251 lines
9.1 KiB
Python
251 lines
9.1 KiB
Python
|
|
# -*- 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()
|