diff --git a/backend/media_rights.py b/backend/media_rights.py index feb49d7..eab8aa6 100644 --- a/backend/media_rights.py +++ b/backend/media_rights.py @@ -159,37 +159,30 @@ def check_rights_coverage(cur: Any, asset_id: int, target_visibility: str) -> st Returns: 'ok' - vorhandene Erklaerung reicht aus - 'insufficient' - Erklaerung vorhanden, aber fuer niedrigere Sichtbarkeit - 'legacy' - Altmedium ohne Erklaerung + 'legacy' - Altmedium ohne Erklaerung (legacy_unreviewed) 'blocked' - durch Admin gesperrt 'no_declaration' - neues Medium ohne Erklaerung (sollte nicht vorkommen) + + Hinweis: Eine P-06-Erklaerung beschreibt den Inhalt (Rechteinhaber, Personen, Musik etc.) + und ist sichtbarkeitsunabhaengig. rights_status='declared' gilt daher fuer alle + Sichtbarkeits-Stufen ohne Levelvergleich. """ cur.execute( - "SELECT rights_status, rights_declared_for_visibility FROM media_assets WHERE id = %s", + "SELECT rights_status FROM media_assets WHERE id = %s", (asset_id,), ) row = cur.fetchone() if not row: return "no_declaration" - # psycopg2 RealDictCursor oder ähnlich - if hasattr(row, "keys"): - rs = row["rights_status"] - rdv = row["rights_declared_for_visibility"] - else: - rs, rdv = row[0], row[1] - - rs = (rs or "").strip().lower() - rdv = (rdv or "").strip().lower() if rdv else None + rs = (row[0] if not hasattr(row, "keys") else row["rights_status"] or "").strip().lower() if rs == "blocked": return "blocked" if rs == "legacy_unreviewed": return "legacy" if rs == "declared": - if rights_covers_target(rdv, target_visibility): - return "ok" - return "insufficient" + return "ok" return "no_declaration" @@ -210,19 +203,6 @@ def assert_rights_for_promotion(cur: Any, asset_id: int, target_visibility: str) "asset_id": asset_id, }, ) - if status == "insufficient": - raise HTTPException( - status_code=400, - detail={ - "code": "RIGHTS_SCOPE_INSUFFICIENT", - "message": ( - f"Die vorhandene Erklaerung gilt nicht fuer die Ziel-Sichtbarkeit '{target_visibility}'. " - "Bitte eine neue Erklaerung fuer diese Sichtbarkeit abgeben." - ), - "asset_id": asset_id, - "target_visibility": target_visibility, - }, - ) if status == "blocked": raise HTTPException( status_code=403, diff --git a/backend/tests/test_media_rights_declaration.py b/backend/tests/test_media_rights_declaration.py index ad6ff0c..a412c1a 100644 --- a/backend/tests/test_media_rights_declaration.py +++ b/backend/tests/test_media_rights_declaration.py @@ -215,28 +215,18 @@ class TestCheckRightsCoverage: assert check_rights_coverage(cur, 1, "private") == "no_declaration" def test_blocked_returns_blocked(self): - cur = self._cur({"rights_status": "blocked", "rights_declared_for_visibility": None}) + cur = self._cur({"rights_status": "blocked"}) assert check_rights_coverage(cur, 1, "private") == "blocked" def test_legacy_returns_legacy(self): - cur = self._cur({"rights_status": "legacy_unreviewed", "rights_declared_for_visibility": None}) + cur = self._cur({"rights_status": "legacy_unreviewed"}) assert check_rights_coverage(cur, 1, "club") == "legacy" - def test_declared_private_covers_private(self): - cur = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "private"}) - assert check_rights_coverage(cur, 1, "private") == "ok" - - def test_declared_private_insufficient_for_club(self): - cur = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "private"}) - assert check_rights_coverage(cur, 1, "club") == "insufficient" - - def test_declared_official_covers_all(self): - cur = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "official"}) - assert check_rights_coverage(cur, 1, "private") == "ok" - cur2 = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "official"}) - assert check_rights_coverage(cur2, 1, "club") == "ok" - cur3 = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "official"}) - assert check_rights_coverage(cur3, 1, "official") == "ok" + def test_declared_covers_any_visibility(self): + # Eine bestehende Erklaerung gilt sichtbarkeitsunabhaengig + for target in ("private", "club", "official"): + cur = self._cur({"rights_status": "declared"}) + assert check_rights_coverage(cur, 1, target) == "ok", f"failed for target={target}" # =========================================================================== @@ -250,31 +240,25 @@ class TestAssertRightsForPromotion: cur.fetchone.return_value = row return cur - def test_ok_passes(self): - cur = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "official"}) - assert_rights_for_promotion(cur, 1, "official") # no raise + def test_ok_passes_for_any_target(self): + for target in ("private", "club", "official"): + cur = self._cur({"rights_status": "declared"}) + assert_rights_for_promotion(cur, 1, target) # no raise def test_legacy_raises_legacy_code(self): - cur = self._cur({"rights_status": "legacy_unreviewed", "rights_declared_for_visibility": None}) + cur = self._cur({"rights_status": "legacy_unreviewed"}) with pytest.raises(HTTPException) as exc: assert_rights_for_promotion(cur, 1, "club") assert exc.value.status_code == 400 assert exc.value.detail["code"] == "LEGACY_REDECLARATION_REQUIRED" def test_blocked_raises_403(self): - cur = self._cur({"rights_status": "blocked", "rights_declared_for_visibility": None}) + cur = self._cur({"rights_status": "blocked"}) with pytest.raises(HTTPException) as exc: assert_rights_for_promotion(cur, 1, "official") assert exc.value.status_code == 403 assert exc.value.detail["code"] == "RIGHTS_BLOCKED" - def test_insufficient_raises_scope_code(self): - cur = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "private"}) - with pytest.raises(HTTPException) as exc: - assert_rights_for_promotion(cur, 1, "official") - assert exc.value.status_code == 400 - assert exc.value.detail["code"] == "RIGHTS_SCOPE_INSUFFICIENT" - # =========================================================================== # 4. PATCH /api/media-assets/{id} – P-06-Promotion via HTTP diff --git a/backend/version.py b/backend/version.py index 9f45726..eb8f5c8 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.80" +APP_VERSION = "0.8.81" BUILD_DATE = "2026-05-11" DB_SCHEMA_VERSION = "20260511048" @@ -31,6 +31,13 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.81", + "date": "2026-05-11", + "changes": [ + "Fix P-06: check_rights_coverage gibt bei rights_status='declared' immer 'ok' zurueck — Erklaerung gilt sichtbarkeitsunabhaengig (Inhalt aendert sich nicht durch Sichtbarkeits-Promotion). 'insufficient'-Pfad entfernt. Tests angepasst.", + ], + }, { "version": "0.8.80", "date": "2026-05-11", diff --git a/frontend/src/version.js b/frontend/src/version.js index 8e7687b..49dfcfb 100644 --- a/frontend/src/version.js +++ b/frontend/src/version.js @@ -1,6 +1,6 @@ // Shinkan Jinkendo Frontend Version -export const APP_VERSION = "0.8.80" +export const APP_VERSION = "0.8.81" export const BUILD_DATE = "2026-05-11" export const PAGE_VERSIONS = {