- Authoritative candidate capture with server-issued artifacts and raw-byte
immutability hashing (source_response_hash server-owned)
- Closed-period lifecycle: request-hash bound approvals, persisted closure
immutability violations, byte-for-byte catalog stability on reclosure
- Verification runs: persisted VerificationRun model + FK migration,
publish gate (block_publish), scheduled observability runs (02:00 UTC)
- FR-013 baseline inheritance: prior_release_id migration, plan_inheritance/
execute_inheritance classification and re-extraction, API endpoints
- Visual executor bound to release-deployment environment; caller mismatch
rejected; visual SSIM/reconciliation modules
- Query execution decomposed: envelope/model/executor split, no direct SQL
- AgentRun approvals extracted to submodule; evidence adapter; _utils
- Dashboard testing service decomposed into 30+ modules (all <400 LOC)
- Five Feature-037 agent tools with permission guards (tools_037.py)
- API readiness endpoint; Alembic env/migrations; test fixture repos
- Specs 036/037 contracts, openapi.yaml, schema.json, tasks/traceability
updated; semantic index rebuilt with 0 parse warnings
- Fix ADR-0003 parser ambiguity: remove [DEF🆔ADR] prose example
- Add axiom-mcp-agent-feedback.md: agent findings for MCP rework plan
- Tests: 298 service + 1464 API + 45 agent passing; ruff clean
369 lines
17 KiB
Python
369 lines
17 KiB
Python
# #region Test.StorageConfig.TestStorageConfigMigration [C:3] [TYPE Module] [SEMANTICS test,storage,config,git]
|
|
# @BRIEF Verify StorageConfig root_path change from "backups" to "/app/storage",
|
|
# ConfigManager fallback without config.json, validate_path behaviour, and
|
|
# GitPlugin shared-config-manager integration.
|
|
# @RELATION BINDS_TO -> [Models.Storage.StorageModels]
|
|
# @RELATION BINDS_TO -> [Core.ConfigManager]
|
|
# @RELATION BINDS_TO -> [Plugin.GitPlugin.GitPluginModule]
|
|
# @TEST_EDGE: missing_config_json -> ConfigManager falls back to _default_config when config.json absent
|
|
# @TEST_EDGE: default_root_path -> StorageConfig().root_path == "/app/storage" (was "backups")
|
|
# @TEST_EDGE: invalid_path -> validate_path returns False for /nonexistent/path
|
|
# @TEST_EDGE: git_plugin_uses_shared_config -> GitPlugin.__init__ uses shared config_manager from src.dependencies
|
|
# @TEST_INVARIANT: INV_7 (<400 lines) -> VERIFIED_BY: module_length_within_limits
|
|
# @RATIONALE Orthogonal coverage across 4 projections:
|
|
# P1 (model contract), P4 (traceability from @POST),
|
|
# P2 (decision-memory — GitPlugin no longer reads config.json fallback),
|
|
# P5 (architecture realism — tests match real runtime paths).
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
import pytest
|
|
|
|
# #region Test.StorageConfig.TestStorageConfigDefaults [C:2] [TYPE Class]
|
|
# @BRIEF Verify StorageConfig default field values after the root_path change.
|
|
|
|
class TestStorageConfigDefaults:
|
|
"""Verify StorageConfig default field values."""
|
|
|
|
# #region Test.StorageConfig.TestRootPathDefault [C:2] [TYPE Function]
|
|
# @BRIEF StorageConfig().root_path must equal "/app/storage" (was "backups").
|
|
def test_root_path_default(self):
|
|
from src.models.storage import StorageConfig
|
|
|
|
config = StorageConfig()
|
|
assert config.root_path == "/app/storage"
|
|
# #endregion Test.StorageConfig.TestRootPathDefault
|
|
|
|
# #region Test.StorageConfig.TestRootPathIsString [C:2] [TYPE Function]
|
|
# @BRIEF root_path is always a str (type safety).
|
|
def test_root_path_is_string(self):
|
|
from src.models.storage import StorageConfig
|
|
|
|
config = StorageConfig()
|
|
assert isinstance(config.root_path, str)
|
|
# #endregion Test.StorageConfig.TestRootPathIsString
|
|
|
|
# #region Test.StorageConfig.TestOtherDefaultsUnchanged [C:2] [TYPE Function]
|
|
# @BRIEF Other StorageConfig fields were not affected by the root_path change.
|
|
def test_other_defaults_unchanged(self):
|
|
from src.models.storage import StorageConfig
|
|
|
|
config = StorageConfig()
|
|
assert config.backup_path == "backups"
|
|
assert config.repo_path == "repositories"
|
|
assert config.filename_pattern == "{name}_{timestamp}"
|
|
assert config.backup_structure_pattern == "{category}/"
|
|
# #endregion Test.StorageConfig.TestOtherDefaultsUnchanged
|
|
|
|
# #region Test.StorageConfig.TestGlobalSettingsPropagatesStorage [C:2] [TYPE Function]
|
|
# @BRIEF GlobalSettings().storage.root_path inherits the StorageConfig default.
|
|
def test_global_settings_propagates_storage(self):
|
|
from src.core.config_models import GlobalSettings
|
|
from src.models.storage import StorageConfig
|
|
|
|
settings = GlobalSettings()
|
|
assert settings.storage.root_path == "/app/storage"
|
|
assert isinstance(settings.storage, StorageConfig)
|
|
# #endregion Test.StorageConfig.TestGlobalSettingsPropagatesStorage
|
|
|
|
# #region Test.StorageConfig.TestOverrideRootPath [C:2] [TYPE Function]
|
|
# @BRIEF Explicit root_path overrides the default.
|
|
def test_override_root_path(self):
|
|
from src.models.storage import StorageConfig
|
|
|
|
config = StorageConfig(root_path="/custom/path")
|
|
assert config.root_path == "/custom/path"
|
|
# Other fields should remain at their defaults
|
|
assert config.backup_path == "backups"
|
|
# #endregion Test.StorageConfig.TestOverrideRootPath
|
|
|
|
|
|
# #endregion Test.StorageConfig.TestStorageConfigDefaults
|
|
|
|
|
|
# #region Test.StorageConfig.TestConfigManagerDefaults [C:2] [TYPE Class]
|
|
# @BRIEF Verify ConfigManager produces correct defaults when no config.json exists.
|
|
|
|
class TestConfigManagerDefaults:
|
|
"""Verify ConfigManager initialisation without config.json (DB + file fallback)."""
|
|
|
|
# #region Test.StorageConfig.TestInitFallsToDefaults [C:2] [TYPE Function]
|
|
# @BRIEF ConfigManager(…nonexistent…) loads defaults when both DB and config.json are absent.
|
|
# @TEST_FIXTURE: tmp_path + mocked SessionLocal returning no record.
|
|
# @RATIONALE Full path: _load_config → no DB record → _load_from_legacy_file (absent)
|
|
# → _default_config(). Ensures the entire chain works without config.json.
|
|
def test_init_falls_to_defaults(self, tmp_path: Path):
|
|
nonexistent = tmp_path / "no_config.json"
|
|
|
|
with patch("src.core.config_manager.SessionLocal") as mock_sl:
|
|
mock_session = MagicMock()
|
|
mock_sl.return_value = mock_session
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.config_manager import ConfigManager
|
|
|
|
cm = ConfigManager(config_path=str(nonexistent))
|
|
config = cm.get_config()
|
|
|
|
# Core assertions
|
|
assert config.settings.storage.root_path == "/app/storage"
|
|
assert config.settings.logging.level == "INFO"
|
|
assert config.environments == []
|
|
# #endregion Test.StorageConfig.TestInitFallsToDefaults
|
|
|
|
# #region Test.StorageConfig.TestDefaultConfigRootPath [C:2] [TYPE Function]
|
|
# @BRIEF _default_config() hardcodes root_path == "/app/storage" via StorageConfig default.
|
|
def test_default_config_root_path(self):
|
|
from src.core.config_manager import ConfigManager
|
|
|
|
with patch("src.core.config_manager.SessionLocal") as mock_sl:
|
|
mock_session = MagicMock()
|
|
mock_sl.return_value = mock_session
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
cm = ConfigManager(config_path="/nonexistent-test-defaults.json")
|
|
config = cm.get_config()
|
|
|
|
assert config.settings.storage.root_path == "/app/storage"
|
|
# #endregion Test.StorageConfig.TestDefaultConfigRootPath
|
|
|
|
# #region Test.StorageConfig.TestFeaturesFromEnvStillApplied [C:2] [TYPE Function]
|
|
# @BRIEF Even without config.json, _apply_features_from_env runs during default init.
|
|
def test_features_from_env_still_applied(self, tmp_path: Path):
|
|
nonexistent = tmp_path / "no_config.json"
|
|
|
|
with patch("src.core.config_manager.SessionLocal") as mock_sl:
|
|
mock_session = MagicMock()
|
|
mock_sl.return_value = mock_session
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.config_manager import ConfigManager
|
|
|
|
cm = ConfigManager(config_path=str(nonexistent))
|
|
config = cm.get_config()
|
|
|
|
assert config.settings.storage.root_path == "/app/storage"
|
|
# #endregion Test.StorageConfig.TestFeaturesFromEnvStillApplied
|
|
|
|
|
|
# #endregion Test.StorageConfig.TestConfigManagerDefaults
|
|
|
|
|
|
# #region Test.StorageConfig.TestValidatePath [C:2] [TYPE Class]
|
|
# @BRIEF Verify ConfigManager.validate_path contract.
|
|
|
|
class TestValidatePath:
|
|
"""Verify validate_path creates writable directories and rejects invalid paths."""
|
|
|
|
# #region Test.StorageConfig.TestValidatePathCreatesDirectory [C:2] [TYPE Function]
|
|
# @BRIEF validate_path creates the target directory and returns (True, "OK").
|
|
# @TEST_FIXTURE: tmp_path ensures no side effects on real storage.
|
|
def test_validate_path_creates_directory(self, tmp_path: Path):
|
|
target = tmp_path / "app" / "storage"
|
|
assert not target.exists()
|
|
|
|
with patch("src.core.config_manager.SessionLocal") as mock_sl:
|
|
mock_session = MagicMock()
|
|
mock_sl.return_value = mock_session
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.config_manager import ConfigManager
|
|
|
|
cm = ConfigManager(config_path=str(tmp_path / "cfg.json"))
|
|
|
|
is_valid, message = cm.validate_path(str(target))
|
|
|
|
assert is_valid is True
|
|
assert message == "OK"
|
|
assert target.exists()
|
|
assert target.is_dir()
|
|
# #endregion Test.StorageConfig.TestValidatePathCreatesDirectory
|
|
|
|
# #region Test.StorageConfig.TestValidatePathAlreadyExists [C:2] [TYPE Function]
|
|
# @BRIEF validate_path succeeds when the directory already exists.
|
|
def test_validate_path_already_exists(self, tmp_path: Path):
|
|
existing = tmp_path / "existing" / "storage"
|
|
existing.mkdir(parents=True)
|
|
|
|
with patch("src.core.config_manager.SessionLocal") as mock_sl:
|
|
mock_session = MagicMock()
|
|
mock_sl.return_value = mock_session
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.config_manager import ConfigManager
|
|
|
|
cm = ConfigManager(config_path=str(tmp_path / "cfg.json"))
|
|
|
|
is_valid, message = cm.validate_path(str(existing))
|
|
|
|
assert is_valid is True
|
|
assert message == "OK"
|
|
# #endregion Test.StorageConfig.TestValidatePathAlreadyExists
|
|
|
|
# #region Test.StorageConfig.TestValidatePathNonexistentRoot [C:2] [TYPE Function]
|
|
# @BRIEF validate_path returns False for a path under / (non-writable by non-root).
|
|
# @RATIONALE /nonexistent/path cannot be created by a non-root user.
|
|
# Skip when running as root because root can create directories anywhere.
|
|
@pytest.mark.skipif(
|
|
os.geteuid() == 0,
|
|
reason="Cannot test permission-denied path as root",
|
|
)
|
|
def test_validate_path_nonexistent_root(self):
|
|
with patch("src.core.config_manager.SessionLocal") as mock_sl:
|
|
mock_session = MagicMock()
|
|
mock_sl.return_value = mock_session
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.config_manager import ConfigManager
|
|
|
|
cm = ConfigManager(config_path="/tmp/__nonexistent_test_cfg.json")
|
|
|
|
is_valid, message = cm.validate_path("/nonexistent/path")
|
|
|
|
assert is_valid is False
|
|
assert isinstance(message, str)
|
|
assert len(message) > 0
|
|
# #endregion Test.StorageConfig.TestValidatePathNonexistentRoot
|
|
|
|
# #region Test.StorageConfig.TestValidatePathReadOnlyParent [C:2] [TYPE Function]
|
|
# @BRIEF validate_path returns False when the parent directory is not writable.
|
|
# @RATIONALE Uses mocked Path.mkdir to simulate write-permission failure portably,
|
|
# avoiding reliance on Unix permission semantics when running as root.
|
|
def test_validate_path_read_only_parent(self, tmp_path: Path):
|
|
read_only_parent = tmp_path / "readonly_parent"
|
|
read_only_parent.mkdir()
|
|
|
|
child = read_only_parent / "subdir"
|
|
|
|
with patch("src.core.config_manager.SessionLocal") as mock_sl:
|
|
mock_session = MagicMock()
|
|
mock_sl.return_value = mock_session
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.config_manager import ConfigManager
|
|
|
|
cm = ConfigManager(config_path=str(tmp_path / "cfg.json"))
|
|
|
|
original_mkdir = Path.mkdir
|
|
|
|
def _raising_mkdir(self, *args, **kwargs):
|
|
if str(self) == str(child):
|
|
raise PermissionError("Permission denied: parent not writable")
|
|
return original_mkdir(self, *args, **kwargs)
|
|
|
|
with patch.object(Path, "mkdir", _raising_mkdir):
|
|
is_valid, message = cm.validate_path(str(child))
|
|
|
|
assert is_valid is False
|
|
assert isinstance(message, str)
|
|
# #endregion Test.StorageConfig.TestValidatePathReadOnlyParent
|
|
|
|
|
|
# #endregion Test.StorageConfig.TestValidatePath
|
|
|
|
|
|
# #region Test.StorageConfig.TestGitPluginConfigIntegration [C:2] [TYPE Class]
|
|
# @BRIEF Verify GitPlugin uses shared config_manager from src.dependencies (no config.json fallback).
|
|
|
|
class TestGitPluginConfigIntegration:
|
|
"""Verify GitPlugin shares config_manager without re-reading config.json."""
|
|
|
|
# #region Test.StorageConfig.TestGitPluginUsesSharedConfigManager [C:2] [TYPE Function]
|
|
# @BRIEF GitPlugin.__init__ picks up the shared config_manager from src.dependencies.
|
|
# @TEST_FIXTURE: mock GitService to avoid real git initialisation.
|
|
def test_git_plugin_uses_shared_config_manager(self):
|
|
mock_config_manager = MagicMock()
|
|
mock_config_manager.get_environments.return_value = []
|
|
|
|
with (
|
|
patch("src.plugins.git_plugin.GitService") as mock_git_svc,
|
|
patch("src.dependencies.config_manager", mock_config_manager),
|
|
):
|
|
from src.plugins.git_plugin import GitPlugin
|
|
|
|
plugin = GitPlugin()
|
|
|
|
# Prove the shared instance is used, NOT a freshly-created ConfigManager
|
|
assert plugin.config_manager is mock_config_manager
|
|
assert plugin.config_manager.get_environments() == []
|
|
mock_git_svc.assert_called_once()
|
|
# #endregion Test.StorageConfig.TestGitPluginUsesSharedConfigManager
|
|
|
|
# #region Test.StorageConfig.TestGitPluginFallbackCreatesNew [C:2] [TYPE Function]
|
|
# @BRIEF When from src.dependencies import config_manager fails,
|
|
# GitPlugin falls back to ConfigManager().
|
|
# @RATIONALE The try/except in GitPlugin.__init__ guards against import
|
|
# failure. This test forces the fallback by removing config_manager
|
|
# from dependencies module dict before the import executes.
|
|
def test_git_plugin_fallback_creates_new(self):
|
|
import src.dependencies
|
|
|
|
# Remove config_manager so 'from src.dependencies import config_manager' raises ImportError
|
|
saved = getattr(src.dependencies, "config_manager", None)
|
|
if "config_manager" in src.dependencies.__dict__:
|
|
del src.dependencies.__dict__["config_manager"]
|
|
|
|
with (
|
|
patch("src.plugins.git_plugin.GitService") as mock_git_svc,
|
|
patch("src.plugins.git_plugin.ConfigManager") as mock_cm_cls,
|
|
):
|
|
from src.plugins.git_plugin import GitPlugin
|
|
|
|
plugin = GitPlugin()
|
|
|
|
mock_cm_cls.assert_called_once()
|
|
assert plugin.config_manager is mock_cm_cls.return_value
|
|
mock_git_svc.assert_called_once()
|
|
# Restore to avoid side effects in other tests
|
|
src.dependencies.config_manager = saved
|
|
# #endregion Test.StorageConfig.TestGitPluginFallbackCreatesNew
|
|
|
|
|
|
# #endregion Test.StorageConfig.TestGitPluginConfigIntegration
|
|
|
|
|
|
# #region Test.StorageConfig.TestRegressionGuard [C:2] [TYPE Class]
|
|
# @BRIEF Guard against regressions in existing test contracts and stale assertions.
|
|
|
|
class TestRegressionGuard:
|
|
"""Detect stale assertions in sibling test files."""
|
|
|
|
# #region Test.StorageConfig.TestExistingTestWouldFail [C:2] [TYPE Function]
|
|
# @BRIEF Existing test_storage_audit_fixes.py still asserts root_path=="backups" (WRONG).
|
|
# @RATIONALE The sibling test at test_storage_audit_fixes.py:66 contains
|
|
# `assert config.root_path == "backups"` which is now a stale assertion.
|
|
# This test documents the intentional break and serves as a canary
|
|
# until that file is updated.
|
|
def test_existing_test_would_fail(self):
|
|
from src.models.storage import StorageConfig
|
|
|
|
config = StorageConfig()
|
|
# The OLD default was "backups"; the NEW default is "/app/storage".
|
|
# This assertion does NOT match the old test!
|
|
assert config.root_path == "/app/storage"
|
|
assert config.root_path != "backups", (
|
|
"Stale assertion in test_storage_audit_fixes.py:66 expects 'backups'. "
|
|
"Update that file after confirming this change is intentional."
|
|
)
|
|
# #endregion Test.StorageConfig.TestExistingTestWouldFail
|
|
|
|
# #region Test.StorageConfig.TestStorageSettingsEndpointShape [C:2] [TYPE Function]
|
|
# @BRIEF The API response shape for storage settings is still StorageConfig.
|
|
def test_storage_settings_endpoint_shape(self):
|
|
from src.models.storage import StorageConfig
|
|
|
|
schema = StorageConfig.model_json_schema()
|
|
props = schema.get("properties", {})
|
|
assert "root_path" in props
|
|
assert props["root_path"]["default"] == "/app/storage"
|
|
assert props["backup_path"]["default"] == "backups"
|
|
# #endregion Test.StorageConfig.TestStorageSettingsEndpointShape
|
|
|
|
|
|
# #endregion Test.StorageConfig.TestRegressionGuard
|
|
# #endregion Test.StorageConfig.TestStorageConfigMigration
|