fix(p06): declared-Status deckt alle Sichtbarkeiten ab (kein Level-Vergleich mehr)
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 58s
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 58s
- check_rights_coverage: rights_status='declared' gibt immer 'ok' zurück (P-06-Erklärung gilt inhaltlich, nicht sichtbarkeitsabhängig) - assert_rights_for_promotion: 'insufficient'-Pfad entfernt - Tests: test_declared_private_insufficient_for_club → test_declared_covers_any_visibility version: 0.8.81 module: media_rights Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6586d3b68b
commit
56e952f084
|
|
@ -159,37 +159,30 @@ def check_rights_coverage(cur: Any, asset_id: int, target_visibility: str) -> st
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
'ok' - vorhandene Erklaerung reicht aus
|
'ok' - vorhandene Erklaerung reicht aus
|
||||||
'insufficient' - Erklaerung vorhanden, aber fuer niedrigere Sichtbarkeit
|
'legacy' - Altmedium ohne Erklaerung (legacy_unreviewed)
|
||||||
'legacy' - Altmedium ohne Erklaerung
|
|
||||||
'blocked' - durch Admin gesperrt
|
'blocked' - durch Admin gesperrt
|
||||||
'no_declaration' - neues Medium ohne Erklaerung (sollte nicht vorkommen)
|
'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(
|
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,),
|
(asset_id,),
|
||||||
)
|
)
|
||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
return "no_declaration"
|
return "no_declaration"
|
||||||
|
|
||||||
# psycopg2 RealDictCursor oder ähnlich
|
rs = (row[0] if not hasattr(row, "keys") else row["rights_status"] or "").strip().lower()
|
||||||
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
|
|
||||||
|
|
||||||
if rs == "blocked":
|
if rs == "blocked":
|
||||||
return "blocked"
|
return "blocked"
|
||||||
if rs == "legacy_unreviewed":
|
if rs == "legacy_unreviewed":
|
||||||
return "legacy"
|
return "legacy"
|
||||||
if rs == "declared":
|
if rs == "declared":
|
||||||
if rights_covers_target(rdv, target_visibility):
|
return "ok"
|
||||||
return "ok"
|
|
||||||
return "insufficient"
|
|
||||||
return "no_declaration"
|
return "no_declaration"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -210,19 +203,6 @@ def assert_rights_for_promotion(cur: Any, asset_id: int, target_visibility: str)
|
||||||
"asset_id": asset_id,
|
"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":
|
if status == "blocked":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=403,
|
status_code=403,
|
||||||
|
|
|
||||||
|
|
@ -215,28 +215,18 @@ class TestCheckRightsCoverage:
|
||||||
assert check_rights_coverage(cur, 1, "private") == "no_declaration"
|
assert check_rights_coverage(cur, 1, "private") == "no_declaration"
|
||||||
|
|
||||||
def test_blocked_returns_blocked(self):
|
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"
|
assert check_rights_coverage(cur, 1, "private") == "blocked"
|
||||||
|
|
||||||
def test_legacy_returns_legacy(self):
|
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"
|
assert check_rights_coverage(cur, 1, "club") == "legacy"
|
||||||
|
|
||||||
def test_declared_private_covers_private(self):
|
def test_declared_covers_any_visibility(self):
|
||||||
cur = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "private"})
|
# Eine bestehende Erklaerung gilt sichtbarkeitsunabhaengig
|
||||||
assert check_rights_coverage(cur, 1, "private") == "ok"
|
for target in ("private", "club", "official"):
|
||||||
|
cur = self._cur({"rights_status": "declared"})
|
||||||
def test_declared_private_insufficient_for_club(self):
|
assert check_rights_coverage(cur, 1, target) == "ok", f"failed for target={target}"
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|
@ -250,31 +240,25 @@ class TestAssertRightsForPromotion:
|
||||||
cur.fetchone.return_value = row
|
cur.fetchone.return_value = row
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
def test_ok_passes(self):
|
def test_ok_passes_for_any_target(self):
|
||||||
cur = self._cur({"rights_status": "declared", "rights_declared_for_visibility": "official"})
|
for target in ("private", "club", "official"):
|
||||||
assert_rights_for_promotion(cur, 1, "official") # no raise
|
cur = self._cur({"rights_status": "declared"})
|
||||||
|
assert_rights_for_promotion(cur, 1, target) # no raise
|
||||||
|
|
||||||
def test_legacy_raises_legacy_code(self):
|
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:
|
with pytest.raises(HTTPException) as exc:
|
||||||
assert_rights_for_promotion(cur, 1, "club")
|
assert_rights_for_promotion(cur, 1, "club")
|
||||||
assert exc.value.status_code == 400
|
assert exc.value.status_code == 400
|
||||||
assert exc.value.detail["code"] == "LEGACY_REDECLARATION_REQUIRED"
|
assert exc.value.detail["code"] == "LEGACY_REDECLARATION_REQUIRED"
|
||||||
|
|
||||||
def test_blocked_raises_403(self):
|
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:
|
with pytest.raises(HTTPException) as exc:
|
||||||
assert_rights_for_promotion(cur, 1, "official")
|
assert_rights_for_promotion(cur, 1, "official")
|
||||||
assert exc.value.status_code == 403
|
assert exc.value.status_code == 403
|
||||||
assert exc.value.detail["code"] == "RIGHTS_BLOCKED"
|
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
|
# 4. PATCH /api/media-assets/{id} – P-06-Promotion via HTTP
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Shinkan Jinkendo Version Information
|
# Shinkan Jinkendo Version Information
|
||||||
|
|
||||||
APP_VERSION = "0.8.80"
|
APP_VERSION = "0.8.81"
|
||||||
BUILD_DATE = "2026-05-11"
|
BUILD_DATE = "2026-05-11"
|
||||||
DB_SCHEMA_VERSION = "20260511048"
|
DB_SCHEMA_VERSION = "20260511048"
|
||||||
|
|
||||||
|
|
@ -31,6 +31,13 @@ MODULE_VERSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
CHANGELOG = [
|
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",
|
"version": "0.8.80",
|
||||||
"date": "2026-05-11",
|
"date": "2026-05-11",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Shinkan Jinkendo Frontend Version
|
// 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 BUILD_DATE = "2026-05-11"
|
||||||
|
|
||||||
export const PAGE_VERSIONS = {
|
export const PAGE_VERSIONS = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user