feat: add slug-only dashboard profile filter and unify backend imports

This commit is contained in:
2026-03-11 12:20:34 +03:00
parent 50001f5ec5
commit a13f75587d
40 changed files with 376 additions and 149 deletions

View File

@@ -8,13 +8,13 @@
from types import SimpleNamespace
import json
from backend.src.dependencies import get_clean_release_repository, get_config_manager
from src.dependencies import get_clean_release_repository, get_config_manager
from datetime import datetime, timezone
from uuid import uuid4
from backend.src.models.clean_release import CleanPolicySnapshot, ComplianceReport, ReleaseCandidate, SourceRegistrySnapshot
from backend.src.services.clean_release.enums import CandidateStatus, ComplianceDecision
from backend.src.scripts.clean_release_cli import main as cli_main
from src.models.clean_release import CleanPolicySnapshot, ComplianceReport, ReleaseCandidate, SourceRegistrySnapshot
from src.services.clean_release.enums import CandidateStatus, ComplianceDecision
from src.scripts.clean_release_cli import main as cli_main
def test_cli_candidate_register_scaffold() -> None:
@@ -302,4 +302,4 @@ def test_cli_release_gate_commands_scaffold() -> None:
assert revoke_exit == 0
# [/DEF:test_clean_release_cli:Module]
# [/DEF:test_clean_release_cli:Module]

View File

@@ -14,8 +14,8 @@ from unittest.mock import MagicMock, patch
import pytest
from backend.src.scripts.clean_release_tui import CleanReleaseTUI, main, tui_main
from backend.src.models.clean_release import CheckFinalStatus
from src.scripts.clean_release_tui import CleanReleaseTUI, main, tui_main
from src.models.clean_release import CheckFinalStatus
@pytest.fixture
@@ -31,7 +31,7 @@ def test_headless_fallback(capsys):
@TEST_EDGE: stdout_unavailable
Tests that non-TTY startup is explicitly refused and wrapper is not invoked.
"""
with mock.patch("backend.src.scripts.clean_release_tui.curses.wrapper") as curses_wrapper_mock:
with mock.patch("src.scripts.clean_release_tui.curses.wrapper") as curses_wrapper_mock:
with mock.patch("sys.stdout.isatty", return_value=False):
exit_code = main()
@@ -43,7 +43,7 @@ def test_headless_fallback(capsys):
assert "Use CLI/API workflow instead" in captured.err
@patch("backend.src.scripts.clean_release_tui.curses")
@patch("src.scripts.clean_release_tui.curses")
def test_tui_initial_render(mock_curses_module, mock_stdscr: MagicMock):
"""
Simulates the initial rendering cycle of the TUI application to ensure
@@ -76,7 +76,7 @@ def test_tui_initial_render(mock_curses_module, mock_stdscr: MagicMock):
assert any("F5 Run" in str(call) for call in addstr_calls)
@patch("backend.src.scripts.clean_release_tui.curses")
@patch("src.scripts.clean_release_tui.curses")
def test_tui_run_checks_f5(mock_curses_module, mock_stdscr: MagicMock):
"""
Simulates pressing F5 to transition into the RUNNING checks flow.
@@ -111,7 +111,7 @@ def test_tui_run_checks_f5(mock_curses_module, mock_stdscr: MagicMock):
assert len(app.violations_list) > 0
@patch("backend.src.scripts.clean_release_tui.curses")
@patch("src.scripts.clean_release_tui.curses")
def test_tui_exit_f10(mock_curses_module, mock_stdscr: MagicMock):
"""
Simulates pressing F10 to exit the application immediately without running checks.
@@ -128,7 +128,7 @@ def test_tui_exit_f10(mock_curses_module, mock_stdscr: MagicMock):
assert app.status == "READY"
@patch("backend.src.scripts.clean_release_tui.curses")
@patch("src.scripts.clean_release_tui.curses")
def test_tui_clear_history_f7(mock_curses_module, mock_stdscr: MagicMock):
"""
Simulates pressing F7 to clear history.
@@ -153,4 +153,3 @@ def test_tui_clear_history_f7(mock_curses_module, mock_stdscr: MagicMock):
# [/DEF:backend.tests.scripts.test_clean_release_tui:Module]

View File

@@ -11,8 +11,8 @@ from __future__ import annotations
import curses
from unittest.mock import MagicMock, patch
from backend.src.models.clean_release import CheckFinalStatus
from backend.src.scripts.clean_release_tui import CleanReleaseTUI, main
from src.models.clean_release import CheckFinalStatus
from src.scripts.clean_release_tui import CleanReleaseTUI, main
def _build_mock_stdscr() -> MagicMock:
@@ -22,7 +22,7 @@ def _build_mock_stdscr() -> MagicMock:
return stdscr
@patch("backend.src.scripts.clean_release_tui.curses")
@patch("src.scripts.clean_release_tui.curses")
def test_tui_f5_dispatches_run_action(mock_curses_module: MagicMock) -> None:
"""F5 should dispatch run action from TUI loop."""
mock_curses_module.KEY_F10 = curses.KEY_F10
@@ -40,7 +40,7 @@ def test_tui_f5_dispatches_run_action(mock_curses_module: MagicMock) -> None:
run_checks_mock.assert_called_once_with()
@patch("backend.src.scripts.clean_release_tui.curses")
@patch("src.scripts.clean_release_tui.curses")
def test_tui_f5_run_smoke_reports_blocked_state(mock_curses_module: MagicMock) -> None:
"""F5 smoke test should expose blocked outcome state after run action."""
mock_curses_module.KEY_F10 = curses.KEY_F10
@@ -76,7 +76,7 @@ def test_tui_non_tty_refuses_startup(capsys) -> None:
assert "Use CLI/API workflow instead" in captured.err
@patch("backend.src.scripts.clean_release_tui.curses")
@patch("src.scripts.clean_release_tui.curses")
def test_tui_f8_blocked_without_facade_binding(mock_curses_module: MagicMock) -> None:
"""F8 should not perform hidden state mutation when facade action is not bound."""
mock_curses_module.KEY_F10 = curses.KEY_F10
@@ -94,4 +94,4 @@ def test_tui_f8_blocked_without_facade_binding(mock_curses_module: MagicMock) ->
assert "F8 disabled" in app.last_error
# [/DEF:test_clean_release_tui_v2:Module]
# [/DEF:test_clean_release_tui_v2:Module]

View File

@@ -9,10 +9,10 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from src.core.database import Base
from src.models.clean_release import ReleaseCandidate, DistributionManifest, CandidateArtifact
from backend.src.services.clean_release.enums import CandidateStatus
from backend.src.services.clean_release.candidate_service import register_candidate
from backend.src.services.clean_release.manifest_service import build_manifest_snapshot
from backend.src.services.clean_release.repository import CleanReleaseRepository
from src.services.clean_release.enums import CandidateStatus
from src.services.clean_release.candidate_service import register_candidate
from src.services.clean_release.manifest_service import build_manifest_snapshot
from src.services.clean_release.repository import CleanReleaseRepository
@pytest.fixture
def db_session():
@@ -43,7 +43,7 @@ def test_candidate_lifecycle_transitions(db_session):
assert candidate.status == CandidateStatus.PREPARED
# Invalid transition: PREPARED -> DRAFT (should raise IllegalTransitionError)
from backend.src.services.clean_release.exceptions import IllegalTransitionError
from src.services.clean_release.exceptions import IllegalTransitionError
with pytest.raises(IllegalTransitionError, match="Forbidden transition"):
candidate.transition_to(CandidateStatus.DRAFT)
@@ -200,4 +200,4 @@ def test_manifest_service_rejects_missing_candidate():
with pytest.raises(ValueError, match="not found"):
build_manifest_snapshot(repository=repository, candidate_id="missing-candidate", created_by="operator")
# [/DEF:test_candidate_manifest_services:Module]
# [/DEF:test_candidate_manifest_services:Module]

View File

@@ -13,17 +13,17 @@ from datetime import datetime, timezone
import pytest
from backend.src.models.clean_release import (
from src.models.clean_release import (
CleanPolicySnapshot,
ComplianceDecision,
DistributionManifest,
ReleaseCandidate,
SourceRegistrySnapshot,
)
from backend.src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
from backend.src.services.clean_release.enums import CandidateStatus, RunStatus
from backend.src.services.clean_release.report_builder import ComplianceReportBuilder
from backend.src.services.clean_release.repository import CleanReleaseRepository
from src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
from src.services.clean_release.enums import CandidateStatus, RunStatus
from src.services.clean_release.report_builder import ComplianceReportBuilder
from src.services.clean_release.repository import CleanReleaseRepository
# [DEF:_seed_with_candidate_policy_registry:Function]
@@ -170,4 +170,4 @@ def test_blocked_run_finalization_blocks_report_builder():
builder.build_report_payload(run, [])
# [/DEF:test_blocked_run_finalization_blocks_report_builder:Function]
# [/DEF:backend.tests.services.clean_release.test_compliance_execution_service:Module]
# [/DEF:backend.tests.services.clean_release.test_compliance_execution_service:Module]

View File

@@ -151,8 +151,8 @@ class _PluginLoaderStub:
def _make_task_manager() -> TaskManager:
plugin_loader = _PluginLoaderStub(CleanReleaseCompliancePlugin())
with patch("backend.src.core.task_manager.manager.TaskPersistenceService") as mock_persistence, patch(
"backend.src.core.task_manager.manager.TaskLogPersistenceService"
with patch("src.core.task_manager.manager.TaskPersistenceService") as mock_persistence, patch(
"src.core.task_manager.manager.TaskLogPersistenceService"
) as mock_log_persistence:
mock_persistence.return_value.load_tasks.return_value = []
mock_persistence.return_value.persist_task = MagicMock()
@@ -247,4 +247,4 @@ async def test_compliance_run_missing_manifest_marks_task_failed():
manager._flusher_thread.join(timeout=2)
# [/DEF:test_compliance_run_missing_manifest_marks_task_failed:Function]
# [/DEF:backend.tests.services.clean_release.test_compliance_task_integration:Module]
# [/DEF:backend.tests.services.clean_release.test_compliance_task_integration:Module]

View File

@@ -9,8 +9,8 @@ from __future__ import annotations
from datetime import datetime, timezone
from backend.src.models.clean_release import ReleaseCandidate
from backend.src.services.clean_release.demo_data_service import (
from src.models.clean_release import ReleaseCandidate
from src.services.clean_release.demo_data_service import (
build_namespaced_id,
create_isolated_repository,
resolve_namespace,
@@ -84,4 +84,4 @@ def test_create_isolated_repository_keeps_mode_data_separate() -> None:
assert real_repo.get_candidate(demo_candidate_id) is None
# [/DEF:test_create_isolated_repository_keeps_mode_data_separate:Function]
# [/DEF:backend.tests.services.clean_release.test_demo_mode_isolation:Module]
# [/DEF:backend.tests.services.clean_release.test_demo_mode_isolation:Module]

View File

@@ -14,10 +14,10 @@ from types import SimpleNamespace
import pytest
from backend.src.models.clean_release import CleanPolicySnapshot, SourceRegistrySnapshot
from backend.src.services.clean_release.exceptions import PolicyResolutionError
from backend.src.services.clean_release.policy_resolution_service import resolve_trusted_policy_snapshots
from backend.src.services.clean_release.repository import CleanReleaseRepository
from src.models.clean_release import CleanPolicySnapshot, SourceRegistrySnapshot
from src.services.clean_release.exceptions import PolicyResolutionError
from src.services.clean_release.policy_resolution_service import resolve_trusted_policy_snapshots
from src.services.clean_release.repository import CleanReleaseRepository
# [DEF:_config_manager:Function]
@@ -102,4 +102,4 @@ def test_resolve_trusted_policy_snapshots_rejects_override_attempt():
)
# [/DEF:test_resolve_trusted_policy_snapshots_rejects_override_attempt:Function]
# [/DEF:backend.tests.services.clean_release.test_policy_resolution_service:Module]
# [/DEF:backend.tests.services.clean_release.test_policy_resolution_service:Module]