v4: 7643 tests passing, 83% raw / 96.9% real coverage (excl __tests__).

12 known failures — all from Agent 3 new unverified tests (mock setup issues):
- 3 dataset_review_routes_extended (DTO field mismatches)
- 1 settings_consolidated (dict key access)
- 1 llm_analysis_service_coverage (rate_limit mock)
- 1 migration_plugin (SessionLocal side_effect exhaustion)
- 1 preview (DB query vs dict key)
- 5 scheduler (datetime timezone + async mock mismatches)

NEW TEST FILES THIS SESSION:
- test_batch_insert_coverage.py — 3 tests
- test_storage_plugin.py — +3 tests
- test_search.py — +2 tests
- test_mapper.py — already 100%
- test_llm_analysis_migration_v1_to_v2.py — 14 tests
- test_llm_async_http.py — +1 test
- test_prompt_builder.py — +1 test
- test_service_datasource.py — +1 test
- test_lang_detect.py — +1 test
- test_scheduler.py — +6 tests
- test_llm_analysis_service_coverage.py — +15 tests
- test_dataset_review_routes_extended.py — +14 tests
- test_settings_consolidated.py — +13 tests

Modules pushed to 100%: _batch_insert, dictionary_entries, service_datasource,
_llm_async_http, prompt_builder, dictionary_crud, _batch_sizer, storage/plugin,
mapper.py

Session: 7194→7643 tests (+449), 80%→83% raw (+3pp), 93.4%→96.9% real (+3.5pp).
Remaining: 12 failures to fix + ~300 statements to reach 98% real coverage.
This commit is contained in:
2026-06-16 09:34:10 +03:00
parent 005ef0f5c7
commit 8a4310169b
28 changed files with 8511 additions and 1 deletions

View File

@@ -0,0 +1,498 @@
# #region TestFacadeAdapter [C:3] [TYPE Module] [SEMANTICS test,clean-release,facade,tui,adapter]
# @BRIEF Tests for TuiFacadeAdapter and CleanReleaseTUI logic methods (non-rendering).
# @RELATION BINDS_TO -> [CleanReleaseTuiScript]
# @TEST_EDGE: facade_build_config_no_policy -> Raises ValueError
# @TEST_EDGE: facade_run_compliance_no_manifests -> Raises ValueError
# @TEST_EDGE: facade_approve_latest_no_reports -> Raises ValueError
# @TEST_EDGE: facade_publish_latest_no_reports -> Raises ValueError
# @TEST_EDGE: resolve_candidate_id_by_env -> Uses CLEAN_TUI_CANDIDATE_ID
# @TEST_EDGE: resolve_candidate_id_fallback -> Uses first candidate
# @TEST_EDGE: resolve_candidate_id_empty -> Returns ""
# @TEST_EDGE: tui_run_checks_error -> FAILED status on exception
# @TEST_EDGE: tui_approve_no_report -> Disabled message when no report_id
# @TEST_EDGE: tui_publish_no_report -> Disabled message when no report_id
# @TEST_EDGE: tui_approve_error -> Error stored on facade failure
# @TEST_EDGE: tui_publish_error -> Error stored on facade failure
# @TEST_EDGE: tui_build_manifest_error -> Error stored on facade failure
# @TEST_EDGE: bootstrap_real_no_path -> Returns early
import os
import pytest
from unittest.mock import MagicMock, patch, PropertyMock
from types import SimpleNamespace
from src.models.clean_release import (
CheckFinalStatus, CheckStageStatus, ComplianceViolation,
ReleaseCandidate, CleanProfilePolicy, ProfileType,
ResourceSourceRegistry, ResourceSourceEntry, RegistryStatus,
ReleaseCandidateStatus, CandidateArtifact,
)
from src.services.clean_release.enums import CandidateStatus
from src.scripts.clean_release_tui import TuiFacadeAdapter, CleanReleaseTUI
# =============================================================================
# TuiFacadeAdapter tests (no curses required)
# =============================================================================
class TestTuiFacadeAdapterBuildConfig:
"""Verify _build_config_manager."""
def test_build_config_with_policy(self):
"""Happy: policy found returns config with active_policy_id."""
repo = MagicMock()
policy = MagicMock()
policy.id = "POL-1"
policy.registry_snapshot_id = "REG-1"
repo.get_active_policy.return_value = policy
adapter = TuiFacadeAdapter(repo)
cm = adapter._build_config_manager()
config = cm.get_config()
assert config.settings.clean_release.active_policy_id == "POL-1"
assert config.settings.clean_release.active_registry_id == "REG-1"
def test_build_config_no_policy_raises(self):
"""Edge: no active policy raises ValueError."""
repo = MagicMock()
repo.get_active_policy.return_value = None
adapter = TuiFacadeAdapter(repo)
with pytest.raises(ValueError, match="Active policy not found"):
adapter._build_config_manager()
class TestTuiFacadeAdapterRunCompliance:
"""Verify run_compliance."""
def test_run_compliance_with_manifests(self):
"""Happy: manifests found, executes compliance."""
repo = MagicMock()
policy = MagicMock()
policy.id = "POL-1"
policy.registry_snapshot_id = "REG-1"
repo.get_active_policy.return_value = policy
manifest_old = MagicMock()
manifest_old.manifest_version = 1
manifest_new = MagicMock()
manifest_new.manifest_version = 2
repo.get_manifests_by_candidate.return_value = [manifest_old, manifest_new]
adapter = TuiFacadeAdapter(repo)
with patch('src.scripts.clean_release_tui.ComplianceExecutionService') as MockService:
mock_instance = MockService.return_value
mock_instance.execute_run.return_value = "compliance_result"
result = adapter.run_compliance(candidate_id="cand-1", actor="test")
repo.get_manifests_by_candidate.assert_called_once_with("cand-1")
assert result == "compliance_result"
def test_run_compliance_no_manifests_raises(self):
"""Edge: no manifests raises ValueError."""
repo = MagicMock()
repo.get_manifests_by_candidate.return_value = []
adapter = TuiFacadeAdapter(repo)
with pytest.raises(ValueError, match="Manifest required before compliance run"):
adapter.run_compliance(candidate_id="cand-1", actor="test")
class TestTuiFacadeAdapterApproveLatest:
"""Verify approve_latest."""
def test_approve_with_reports(self):
"""Happy: reports found, approves."""
repo = MagicMock()
r1 = MagicMock()
r1.candidate_id = "cand-1"
r1.generated_at = "2024-01-01T00:00:00"
r2 = MagicMock()
r2.candidate_id = "cand-1"
r2.generated_at = "2024-06-01T00:00:00"
repo.reports = {"r1": r1, "r2": r2}
adapter = TuiFacadeAdapter(repo)
with patch('src.scripts.clean_release_tui.approve_candidate') as mock_approve:
adapter.approve_latest(candidate_id="cand-1", actor="test")
mock_approve.assert_called_once()
def test_approve_no_reports_raises(self):
"""Edge: no reports raises ValueError."""
repo = MagicMock()
repo.reports = {}
adapter = TuiFacadeAdapter(repo)
with pytest.raises(ValueError, match="No compliance report available for approval"):
adapter.approve_latest(candidate_id="cand-1", actor="test")
class TestTuiFacadeAdapterPublishLatest:
"""Verify publish_latest."""
def test_publish_with_reports(self):
"""Happy: reports found, publishes."""
repo = MagicMock()
r1 = MagicMock()
r1.candidate_id = "cand-1"
r1.generated_at = "2024-01-01T00:00:00"
repo.reports = {"r1": r1}
adapter = TuiFacadeAdapter(repo)
with patch('src.scripts.clean_release_tui.publish_candidate') as mock_publish:
adapter.publish_latest(candidate_id="cand-1", actor="test")
mock_publish.assert_called_once()
def test_publish_no_reports_raises(self):
"""Edge: no reports raises ValueError."""
repo = MagicMock()
repo.reports = {}
adapter = TuiFacadeAdapter(repo)
with pytest.raises(ValueError, match="No compliance report available for publication"):
adapter.publish_latest(candidate_id="cand-1", actor="test")
class TestTuiFacadeAdapterBuildManifest:
"""Verify build_manifest."""
def test_build_manifest_delegates(self):
"""Happy: delegates to build_manifest_snapshot."""
repo = MagicMock()
adapter = TuiFacadeAdapter(repo)
with patch('src.scripts.clean_release_tui.build_manifest_snapshot') as mock_build:
adapter.build_manifest(candidate_id="cand-1", actor="test")
mock_build.assert_called_once_with(
repository=repo, candidate_id="cand-1", created_by="test",
)
class TestTuiFacadeAdapterGetOverview:
"""Verify get_overview."""
def test_get_overview_full(self):
"""Happy: all data present."""
repo = MagicMock()
candidate = MagicMock()
candidate.id = "cand-1"
repo.get_candidate.return_value = candidate
manifest = MagicMock()
manifest.manifest_version = 2
repo.get_manifests_by_candidate.return_value = [manifest]
run = MagicMock()
run.candidate_id = "cand-1"
run.requested_at = "2024-06-01T00:00:00"
repo.check_runs = {"r1": run}
report = MagicMock()
report.run_id = run.id
repo.reports = {"rep1": report}
approval = MagicMock()
approval.candidate_id = "cand-1"
approval.decided_at = "2024-06-02T00:00:00"
repo.approval_decisions = [approval]
publication = MagicMock()
publication.candidate_id = "cand-1"
publication.published_at = "2024-06-03T00:00:00"
repo.publication_records = [publication]
policy = MagicMock()
policy.internal_source_registry_ref = "REG-1"
repo.get_active_policy.return_value = policy
registry = MagicMock()
repo.get_registry.return_value = registry
adapter = TuiFacadeAdapter(repo)
overview = adapter.get_overview(candidate_id="cand-1")
assert overview["candidate"] is candidate
assert overview["manifest"] is manifest
assert overview["run"] is run
assert overview["report"] is report
assert overview["approval"] is approval
assert overview["publication"] is publication
assert overview["policy"] is policy
assert overview["registry"] is registry
def test_get_overview_empty(self):
"""Edge: no data at all, returns safe defaults."""
repo = MagicMock()
repo.get_candidate.return_value = None
repo.get_manifests_by_candidate.return_value = []
repo.check_runs = {}
repo.reports = {}
repo.get_active_policy.return_value = None
adapter = TuiFacadeAdapter(repo)
overview = adapter.get_overview(candidate_id="nonexistent")
assert overview["candidate"] is None
assert overview["manifest"] is None
assert overview["run"] is None
assert overview["report"] is None
assert overview["approval"] is None
assert overview["publication"] is None
assert overview["policy"] is None
assert overview["registry"] is None
# =============================================================================
# CleanReleaseTUI logic tests (curses mocked at module level)
# =============================================================================
@pytest.fixture
def mock_stdscr():
stdscr = MagicMock()
stdscr.getmaxyx.return_value = (40, 100)
stdscr.getch.return_value = -1
return stdscr
class TestResolveCandidateId:
"""Verify _resolve_candidate_id."""
@patch("src.scripts.clean_release_tui.curses")
def test_returns_env_var(self, mock_curses, mock_stdscr):
"""Edge: CLEAN_TUI_CANDIDATE_ID set returns env value."""
with patch.dict(os.environ, {"CLEAN_TUI_CANDIDATE_ID": "env-cand"}, clear=False):
app = CleanReleaseTUI(mock_stdscr)
# After init the candidate_id is already resolved, verify it
assert app.candidate_id == "env-cand"
@patch("src.scripts.clean_release_tui.curses")
def test_falls_back_to_first_candidate(self, mock_curses, mock_stdscr):
"""Edge: no env var, uses first candidate key."""
app = CleanReleaseTUI(mock_stdscr)
# Demo mode creates a candidate "2026.03.03-rc1"
assert app.candidate_id == "2026.03.03-rc1"
@patch("src.scripts.clean_release_tui.curses")
def test_returns_empty_when_no_candidates(self, mock_curses):
"""Edge: no env var and no candidates returns empty string."""
stdscr = MagicMock()
stdscr.getmaxyx.return_value = (40, 100)
# Inject a repo with no candidates
from src.services.clean_release.repository import CleanReleaseRepository
from src.scripts.clean_release_tui import TuiFacadeAdapter
with patch.dict(os.environ, {"CLEAN_TUI_MODE": "real"}, clear=False):
with patch("src.scripts.clean_release_tui.CleanReleaseTUI._bootstrap_real_repository"):
app = CleanReleaseTUI(stdscr)
# In real mode with no bootstrap, no candidates exist
resolved = app._resolve_candidate_id()
# _resolve_candidate_id is called during __init__, so the result is already in app.candidate_id
assert app.candidate_id == ""
class TestTuiBuildManifestError:
"""Verify build_manifest error handling."""
@patch("src.scripts.clean_release_tui.curses")
def test_build_manifest_captures_error(self, mock_curses, mock_stdscr):
"""Edge: facade raises, error stored."""
app = CleanReleaseTUI(mock_stdscr)
# Replace facade with one that raises
app.facade = MagicMock()
app.facade.build_manifest.side_effect = ValueError("Build failed")
app.build_manifest()
assert "Build failed" in app.last_error
class TestTuiApproveError:
"""Verify approve_latest error handling."""
@patch("src.scripts.clean_release_tui.curses")
def test_approve_no_report_id(self, mock_curses, mock_stdscr):
"""Edge: no report_id sets disabled message."""
app = CleanReleaseTUI(mock_stdscr)
app.report_id = None
app.approve_latest()
assert "F8 disabled" in app.last_error
@patch("src.scripts.clean_release_tui.curses")
def test_approve_facade_error(self, mock_curses, mock_stdscr):
"""Edge: facade raises, error stored."""
app = CleanReleaseTUI(mock_stdscr)
app.report_id = "rep-1"
app.facade = MagicMock()
app.facade.approve_latest.side_effect = ValueError("Approve failed")
app.approve_latest()
assert "Approve failed" in app.last_error
class TestTuiPublishError:
"""Verify publish_latest error handling."""
@patch("src.scripts.clean_release_tui.curses")
def test_publish_no_report_id(self, mock_curses, mock_stdscr):
"""Edge: no report_id sets disabled message."""
app = CleanReleaseTUI(mock_stdscr)
app.report_id = None
app.publish_latest()
assert "F9 disabled" in app.last_error
@patch("src.scripts.clean_release_tui.curses")
def test_publish_facade_error(self, mock_curses, mock_stdscr):
"""Edge: facade raises, error stored."""
app = CleanReleaseTUI(mock_stdscr)
app.report_id = "rep-1"
app.facade = MagicMock()
app.facade.publish_latest.side_effect = ValueError("Publish failed")
app.publish_latest()
assert "Publish failed" in app.last_error
class TestTuiRunChecksError:
"""Verify run_checks error handling."""
@patch("src.scripts.clean_release_tui.curses")
def test_run_checks_facade_error(self, mock_curses, mock_stdscr):
"""Edge: facade raises during run_checks -> FAILED status."""
app = CleanReleaseTUI(mock_stdscr)
app.facade = MagicMock()
app.facade.run_compliance.side_effect = RuntimeError("Compliance crashed")
app.run_checks()
assert app.status == CheckFinalStatus.FAILED
assert "Compliance crashed" in app.last_error
class TestTuiClearHistory:
"""Verify clear_history resets state."""
@patch("src.scripts.clean_release_tui.curses")
def test_clear_history_resets_state(self, mock_curses, mock_stdscr):
"""Edge: after running checks, clear_history resets."""
app = CleanReleaseTUI(mock_stdscr)
app.status = CheckFinalStatus.BLOCKED
app.report_id = "rep-1"
app.violations_list = [MagicMock()]
app.checks_progress = [{"stage": "test", "status": "FAIL"}]
app.last_error = "some error"
app.clear_history()
assert app.status == "READY"
assert app.report_id is None
assert app.violations_list == []
assert app.checks_progress == []
assert app.last_error is None
# =============================================================================
# _bootstrap_real_repository tests
# =============================================================================
class TestBootstrapRealRepository:
"""Verify _bootstrap_real_repository."""
@patch("src.scripts.clean_release_tui.curses")
def test_no_bootstrap_path_returns_early(self, mock_curses, mock_stdscr):
"""Edge: CLEAN_TUI_BOOTSTRAP_JSON not set returns early."""
from src.services.clean_release.repository import CleanReleaseRepository
app = CleanReleaseTUI(mock_stdscr)
repo = CleanReleaseRepository()
app._bootstrap_real_repository(repo)
# No bootstrap loaded -> no candidates
assert len(repo.candidates) == 0
@patch("src.scripts.clean_release_tui.curses")
def test_bootstrap_with_valid_json(self, mock_curses, mock_stdscr, tmp_path):
"""Happy: valid bootstrap.json loads candidate, registry and policy."""
import json
bootstrap = tmp_path / "bootstrap.json"
bootstrap.write_text(json.dumps({
"candidate_id": "test-cand",
"version": "1.0.0",
"source_snapshot_ref": "git-ref",
"created_by": "test",
"allowed_hosts": ["repo.example.com"],
}), encoding="utf-8")
from src.services.clean_release.repository import CleanReleaseRepository
app = CleanReleaseTUI(mock_stdscr)
repo = CleanReleaseRepository()
with patch.dict(os.environ, {
"CLEAN_TUI_BOOTSTRAP_JSON": str(bootstrap),
}, clear=False):
app._bootstrap_real_repository(repo)
assert repo.get_candidate("test-cand") is not None
assert len(repo.registries) > 0
assert len(repo.policies) > 0
assert repo.get_candidate("test-cand").status == "DRAFT"
@patch("src.scripts.clean_release_tui.curses")
def test_bootstrap_with_artifacts_transitions_to_prepared(self, mock_curses, mock_stdscr, tmp_path):
"""Happy: artifacts imported transitions candidate to PREPARED."""
import json
bootstrap = tmp_path / "bootstrap.json"
bootstrap.write_text(json.dumps({
"candidate_id": "test-cand-2",
"version": "2.0.0",
"source_snapshot_ref": "git-ref-2",
"created_by": "test",
"allowed_hosts": [],
}), encoding="utf-8")
artifacts = tmp_path / "artifacts.json"
artifacts.write_text(json.dumps({
"artifacts": [{
"id": "art-1", "path": "dist/file.tar.gz",
"sha256": "abc123", "size": 512, "category": "core",
"source_uri": "https://repo.example.com/file.tar.gz",
"source_host": "repo.example.com",
}]
}), encoding="utf-8")
from src.services.clean_release.repository import CleanReleaseRepository
app = CleanReleaseTUI(mock_stdscr)
repo = CleanReleaseRepository()
with patch.dict(os.environ, {
"CLEAN_TUI_BOOTSTRAP_JSON": str(bootstrap),
"CLEAN_TUI_ARTIFACTS_JSON": str(artifacts),
}, clear=False):
app._bootstrap_real_repository(repo)
assert repo.get_candidate("test-cand-2").status == "PREPARED"
artifacts_list = repo.get_artifacts_by_candidate("test-cand-2")
assert len(artifacts_list) == 1
assert artifacts_list[0].detected_category == "core"
@patch("src.scripts.clean_release_tui.curses")
def test_bootstrap_with_entries_saves_policy(self, mock_curses, mock_stdscr, tmp_path):
"""Edge: entries present saves registry and policy."""
import json
bootstrap = tmp_path / "bootstrap.json"
bootstrap.write_text(json.dumps({
"candidate_id": "test-cand-3",
"version": "3.0.0",
"source_snapshot_ref": "git-ref-3",
"created_by": "test",
"allowed_hosts": ["repo.example.com"],
"registry_id": "REG-TEST",
"policy_id": "POL-TEST",
}), encoding="utf-8")
from src.services.clean_release.repository import CleanReleaseRepository
app = CleanReleaseTUI(mock_stdscr)
repo = CleanReleaseRepository()
with patch.dict(os.environ, {
"CLEAN_TUI_BOOTSTRAP_JSON": str(bootstrap),
}, clear=False):
app._bootstrap_real_repository(repo)
assert repo.get_registry("REG-TEST") is not None
policy = repo.get_policy("POL-TEST")
assert policy is not None
# #endregion TestFacadeAdapter