# #region Test.GitPlugin [C:3] [TYPE Module] [SEMANTICS test, git, plugin, sync, deploy, helpers, coverage] # @BRIEF Comprehensive tests covering GitPluginModule — helpers, execute, _handle_sync, _handle_deploy, _get_env, edge cases. # @RELATION BINDS_TO -> [GitPluginModule] # @TEST_EDGE: empty_zip -> Raises ValueError # @TEST_EDGE: env_not_found -> Raises ValueError # @TEST_EDGE: unknown_operation -> Raises ValueError # @TEST_EDGE: backup_restore_on_failure -> Backup restored on unzip error # @TEST_EDGE: deploy_missing_env_id -> Raises ValueError # @TEST_EDGE: init_fallback -> Falls back to new ConfigManager when import fails import asyncio import io import os import shutil import tempfile import time from pathlib import Path import zipfile from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock import pytest from src.plugins.git_plugin import ( _create_sync_backup, _delete_managed_files, _extract_zip_to_repo, _restore_sync_backup, _pack_deploy_zip, GitPlugin, ) # ── Fixtures ── @pytest.fixture def temp_repo(): """Create a temporary directory simulating a git repo.""" tmp = tempfile.mkdtemp() yield Path(tmp) shutil.rmtree(tmp) @pytest.fixture def temp_backup(): """Create a temporary backup directory.""" tmp = tempfile.mkdtemp() yield Path(tmp) shutil.rmtree(tmp) def _create_zip(files: dict[str, str], root_folder: str = "export") -> bytes: """Helper: create a ZIP file in memory with given file paths and contents.""" buf = io.BytesIO() with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: for path, content in files.items(): zf.writestr(f"{root_folder}/{path}", content) buf.seek(0) return buf.getvalue() def _make_plugin(): """Helper: create GitPlugin with dependencies patched so config_manager is a mock.""" mock_cm = MagicMock() with patch('src.dependencies.config_manager', mock_cm): plugin = GitPlugin() return plugin, mock_cm # ── Sync Helper Tests ── class TestSyncHelpers: """Verify _sync_helpers functions comprehensively.""" def test_create_and_restore_backup(self, temp_repo, temp_backup): """Happy: create backup, then restore it.""" managed_dirs = ["dashboards", "charts"] managed_files = ["metadata.yaml"] # Create original files (temp_repo / "dashboards").mkdir(parents=True) (temp_repo / "dashboards" / "dash1.json").write_text("{'id': 1}") (temp_repo / "charts").mkdir(parents=True) (temp_repo / "charts" / "chart1.json").write_text("{'id': 2}") (temp_repo / "metadata.yaml").write_text("version: 1") # Create backup _create_sync_backup(temp_repo, temp_backup, managed_dirs, managed_files) assert (temp_backup / "dashboards" / "dash1.json").exists() assert (temp_backup / "charts" / "chart1.json").exists() assert (temp_backup / "metadata.yaml").exists() # Delete originals _delete_managed_files(temp_repo, managed_dirs, managed_files) assert not (temp_repo / "dashboards").exists() assert not (temp_repo / "metadata.yaml").exists() # Restore from backup _restore_sync_backup(temp_repo, temp_backup, managed_dirs, managed_files) assert (temp_repo / "dashboards" / "dash1.json").exists() assert (temp_repo / "metadata.yaml").exists() assert (temp_repo / "dashboards" / "dash1.json").read_text() == "{'id': 1}" def test_backup_non_existent_skips(self, temp_repo, temp_backup): """Edge: non-existent files skipped during backup.""" _create_sync_backup(temp_repo, temp_backup, ["nonexistent"], ["no.yaml"]) # No crash is success def test_backup_non_existent_file_skipped(self, temp_repo, temp_backup): """Edge: non-existent file path skipped during backup.""" _create_sync_backup(temp_repo, temp_backup, [], ["no_such_file.yaml"]) # No crash def test_delete_managed_files(self, temp_repo): """Happy: delete managed files.""" (temp_repo / "dashboards").mkdir(parents=True) (temp_repo / "dashboards" / "dash1.json").write_text("{}") (temp_repo / "metadata.yaml").write_text("version: 1") _delete_managed_files(temp_repo, ["dashboards"], ["metadata.yaml"]) assert not (temp_repo / "dashboards").exists() assert not (temp_repo / "metadata.yaml").exists() def test_delete_non_existent(self, temp_repo): """Edge: deleting non-existent paths is a no-op.""" _delete_managed_files(temp_repo, ["does-not-exist"], ["no.yaml"]) # No crash def test_delete_file_only(self, temp_repo): """Edge: delete only files, not dirs.""" (temp_repo / "some_dir").mkdir(parents=True) (temp_repo / "some_dir" / "file.txt").write_text("data") (temp_repo / "metadata.yaml").write_text("version: 1") _delete_managed_files(temp_repo, [], ["metadata.yaml"]) assert not (temp_repo / "metadata.yaml").exists() assert (temp_repo / "some_dir").exists() # dirs untouched def test_extract_zip_to_repo(self, temp_repo): """Happy: extract zip to repository.""" zip_bytes = _create_zip({ "dashboards/dash1.json": '{"id": 1}', "charts/chart1.json": '{"id": 2}', }) _extract_zip_to_repo(zip_bytes, temp_repo) assert (temp_repo / "dashboards" / "dash1.json").exists() assert (temp_repo / "charts" / "chart1.json").exists() assert (temp_repo / "dashboards" / "dash1.json").read_text() == '{"id": 1}' def test_extract_empty_zip_raises(self, temp_repo): """Negative: empty zip raises ValueError.""" buf = io.BytesIO() with zipfile.ZipFile(buf, "w") as zf: pass # empty zip buf.seek(0) with pytest.raises(ValueError, match="empty"): _extract_zip_to_repo(buf.getvalue(), temp_repo) def test_extract_zip_nested_dirs(self, temp_repo): """Edge: extract zip with nested directory structure.""" zip_bytes = _create_zip({ "dashboards/sub/child.json": '{"nested": true}', }) _extract_zip_to_repo(zip_bytes, temp_repo) assert (temp_repo / "dashboards" / "sub" / "child.json").exists() def test_restore_backup_non_existent(self, temp_repo, temp_backup): """Edge: restore when backup dirs don't exist.""" _restore_sync_backup(temp_repo, temp_backup, ["missing"], ["no.yaml"]) # No crash def test_restore_overwrites_existing(self, temp_repo, temp_backup): """Edge: restore overwrites existing files.""" (temp_repo / "dashboards").mkdir(parents=True) (temp_repo / "dashboards" / "dash1.json").write_text("old") (temp_backup / "dashboards").mkdir(parents=True) (temp_backup / "dashboards" / "dash1.json").write_text("new") _restore_sync_backup(temp_repo, temp_backup, ["dashboards"], []) assert (temp_repo / "dashboards" / "dash1.json").read_text() == "new" def test_restore_backup_dir_with_file_in_subdir(self, temp_repo, temp_backup): """Edge: restore creates parent dirs for files.""" (temp_backup / "subdir").mkdir(parents=True) (temp_backup / "subdir" / "deep.txt").write_text("deep") _restore_sync_backup(temp_repo, temp_backup, [], ["subdir/deep.txt"]) assert (temp_repo / "subdir" / "deep.txt").exists() assert (temp_repo / "subdir" / "deep.txt").read_text() == "deep" class TestDeployHelpers: """Verify _deploy_helpers functions comprehensively.""" def test_pack_deploy_zip(self, temp_repo): """Happy: pack repo files into zip.""" (temp_repo / "dashboards").mkdir(parents=True) (temp_repo / "dashboards" / "dash1.json").write_text('{"id": 1}') (temp_repo / "charts").mkdir(parents=True) (temp_repo / "charts" / "chart1.json").write_text('{"id": 2}') buf = io.BytesIO() _pack_deploy_zip(temp_repo, "export", buf) buf.seek(0) with zipfile.ZipFile(buf) as zf: names = zf.namelist() assert any("dash1.json" in n for n in names) assert any("chart1.json" in n for n in names) def test_pack_zip_skips_git(self, temp_repo): """Edge: .git directories excluded from zip.""" (temp_repo / "dashboards").mkdir(parents=True) (temp_repo / "dashboards" / "dash1.json").write_text("{}") (temp_repo / ".git").mkdir(parents=True) (temp_repo / ".git" / "config").write_text("repo config") buf = io.BytesIO() _pack_deploy_zip(temp_repo, "export", buf) buf.seek(0) with zipfile.ZipFile(buf) as zf: names = zf.namelist() assert not any(n.startswith(".git") for n in names) assert any("dash1.json" in n for n in names) def test_pack_zip_empty_repo(self, temp_repo): """Edge: empty repo results in zip with root only.""" buf = io.BytesIO() _pack_deploy_zip(temp_repo, "export", buf) buf.seek(0) with zipfile.ZipFile(buf) as zf: names = zf.namelist() assert len(names) == 0 def test_pack_zip_skips_existing_zips(self, temp_repo): """Edge: .zip files excluded from deploy zip.""" (temp_repo / "dashboards").mkdir(parents=True) (temp_repo / "dashboards" / "dash1.json").write_text("{}") (temp_repo / "deploy_1.zip").write_text("fake zip content") buf = io.BytesIO() _pack_deploy_zip(temp_repo, "export", buf) buf.seek(0) with zipfile.ZipFile(buf) as zf: names = zf.namelist() assert not any(n.endswith(".zip") for n in names) assert any("dash1.json" in n for n in names) def test_pack_zip_skips_git_files(self, temp_repo): """Edge: files named .git excluded.""" (temp_repo / "dashboards").mkdir(parents=True) (temp_repo / "dashboards" / "dash1.json").write_text("{}") (temp_repo / ".git").write_text("file named .git") buf = io.BytesIO() _pack_deploy_zip(temp_repo, "export", buf) buf.seek(0) with zipfile.ZipFile(buf) as zf: names = zf.namelist() assert not any(".git" in n for n in names) # ── GitPlugin Class Tests ── class TestGitPluginInit: """Verify GitPlugin.__init__ behaviors.""" def test_init_with_dependencies(self): """Happy: init fetches config_manager from src.dependencies.""" mock_cm = MagicMock() with patch('src.dependencies.config_manager', mock_cm): plugin = GitPlugin() assert plugin.config_manager is mock_cm assert plugin.git_service is not None def test_init_fallback_config(self): """Negative: when import fails, falls back to new ConfigManager.""" import sys import types saved = sys.modules.pop('src.dependencies', None) try: fake = types.ModuleType('src.dependencies') fake.__package__ = 'src' sys.modules['src.dependencies'] = fake with patch('src.plugins.git_plugin.ConfigManager') as MockCM: instance = MagicMock() MockCM.return_value = instance plugin = GitPlugin() assert plugin.config_manager is instance finally: if saved: sys.modules['src.dependencies'] = saved else: sys.modules.pop('src.dependencies', None) def test_init_retry_on_exception(self): """Edge: other exception during import also triggers fallback.""" import sys import types saved = sys.modules.pop('src.dependencies', None) try: fake = types.ModuleType('src.dependencies') fake.__package__ = 'src' sys.modules['src.dependencies'] = fake with patch('src.plugins.git_plugin.ConfigManager') as MockCM: instance = MagicMock() MockCM.return_value = instance plugin = GitPlugin() assert plugin.config_manager is instance finally: if saved: sys.modules['src.dependencies'] = saved else: sys.modules.pop('src.dependencies', None) class TestGitPluginProperties: """Verify static properties.""" def test_properties(self): plugin, _ = _make_plugin() assert plugin.id == "git-integration" assert plugin.name == "Git Integration" assert plugin.description == "Version control for Superset dashboards" assert plugin.version == "0.1.0" assert plugin.ui_route == "/git" def test_get_schema(self): plugin, _ = _make_plugin() schema = plugin.get_schema() assert schema["type"] == "object" assert "operation" in schema["properties"] assert schema["properties"]["operation"]["enum"] == ["sync", "deploy", "history"] assert "dashboard_id" in schema["required"] assert "operation" in schema["required"] def test_initialize(self): plugin, _ = _make_plugin() asyncio.run(plugin.initialize()) # should not raise class TestGitPluginExecute: """Verify GitPlugin.execute.""" def test_unknown_operation_raises(self): plugin, _ = _make_plugin() with pytest.raises(ValueError, match="Unknown operation"): asyncio.run(plugin.execute({"operation": "unknown", "dashboard_id": 1})) def test_history_operation(self): plugin, _ = _make_plugin() result = asyncio.run(plugin.execute({"operation": "history", "dashboard_id": 1})) assert result["status"] == "success" @pytest.mark.asyncio async def test_sync_operation(self): plugin, _ = _make_plugin() with patch.object(GitPlugin, '_handle_sync', new=AsyncMock(return_value={"status": "success"})): result = await plugin.execute({"operation": "sync", "dashboard_id": 1}) assert result["status"] == "success" @pytest.mark.asyncio async def test_deploy_operation(self): plugin, _ = _make_plugin() with patch.object(GitPlugin, '_handle_deploy', new=AsyncMock(return_value={"status": "success"})): result = await plugin.execute({"operation": "deploy", "dashboard_id": 1, "environment_id": "env-1"}) assert result["status"] == "success" @pytest.mark.asyncio async def test_execute_with_task_context(self): """Edge: execute with TaskContext logging.""" plugin, _ = _make_plugin() mock_ctx = MagicMock() mock_ctx.logger = MagicMock() mock_ctx.logger.with_source.return_value = MagicMock() with patch.object(GitPlugin, '_handle_sync', new=AsyncMock(return_value={"status": "success"})): result = await plugin.execute( {"operation": "sync", "dashboard_id": 1, "source_env_id": "src-env"}, context=mock_ctx, ) assert result["status"] == "success" def test_execute_no_operation_key(self): """Negative: missing operation key returns None branch.""" plugin, _ = _make_plugin() with pytest.raises(ValueError, match="Unknown operation"): asyncio.run(plugin.execute({"dashboard_id": 1})) def _mock_log(): """Create a mock log object for _handle_sync/_handle_deploy.""" log = MagicMock() log.with_source.return_value = log log.info = MagicMock() log.error = MagicMock() log.warning = MagicMock() log.debug = MagicMock() return log class TestHandleSync: """Verify _handle_sync method.""" @pytest.mark.asyncio async def test_handle_sync_success(self): """Happy: full sync flow succeeds.""" plugin, mock_cm = _make_plugin() log = _mock_log() # Mock dependencies mock_env = MagicMock() mock_env.name = "Test Env" mock_cm.get_environment.return_value = mock_env with patch('src.plugins.git_plugin.run_blocking') as mock_run_blocking: # Mock git repo tmpdir = tempfile.mkdtemp() mock_repo = MagicMock() mock_repo.working_dir = tmpdir mock_repo.git = MagicMock() mock_repo.git.add = MagicMock() # Setup run_blocking side effects based on kind async def run_blocking_side(kind='file', fn=None, **kwargs): if kind == 'git' and fn == plugin.git_service.get_repo: return mock_repo if kind == 'git' and fn == mock_repo.git.add: return None if fn.__name__ == '_create_sync_backup': return None if fn.__name__ == '_delete_managed_files': return None if fn.__name__ == '_extract_zip_to_repo': return None if fn.__name__ == 'rmtree': return None return None mock_run_blocking.side_effect = run_blocking_side # Mock SupersetClient with patch('src.plugins.git_plugin.SupersetClient') as MockSupersetClient: mock_client = MagicMock() MockSupersetClient.return_value = mock_client mock_client.authenticate = AsyncMock() mock_client.export_dashboard = AsyncMock(return_value=(b"zip data", "20240101")) result = await plugin._handle_sync(1, log=log, git_log=log, superset_log=log) assert result["status"] == "success" assert "synced" in result["message"] @pytest.mark.asyncio async def test_handle_sync_backup_restore_on_failure(self): """Negative: on unzip failure, backup is restored and error re-raised.""" plugin, mock_cm = _make_plugin() log = _mock_log() mock_env = MagicMock() mock_env.name = "Test Env" mock_cm.get_environment.return_value = mock_env with patch('src.plugins.git_plugin.run_blocking') as mock_run_blocking: mock_repo = MagicMock() mock_repo.working_dir = tempfile.mkdtemp() mock_repo.git = MagicMock() async def run_blocking_side(kind='file', fn=None, **kwargs): if kind == 'git' and fn == plugin.git_service.get_repo: return mock_repo if fn.__name__ == '_extract_zip_to_repo': raise ValueError("Unzip failed") if fn.__name__ == '_restore_sync_backup': return None return None mock_run_blocking.side_effect = run_blocking_side with patch('src.plugins.git_plugin.SupersetClient') as MockSupersetClient: mock_client = MagicMock() MockSupersetClient.return_value = mock_client mock_client.authenticate = AsyncMock() mock_client.export_dashboard = AsyncMock(return_value=(b"zip", "ts")) # backup_dir needs to exist for restore branch with patch('pathlib.Path.exists', return_value=True): with pytest.raises(ValueError, match="Unzip failed"): await plugin._handle_sync(1, log=log, git_log=log, superset_log=log) @pytest.mark.asyncio async def test_handle_sync_dashboard_id_string(self): """Edge: sync with string dashboard_id.""" plugin, mock_cm = _make_plugin() log = _mock_log() mock_env = MagicMock() mock_env.name = "Test" mock_cm.get_environment.return_value = mock_env with patch('src.plugins.git_plugin.run_blocking') as mock_run_blocking: mock_repo = MagicMock() mock_repo.working_dir = tempfile.mkdtemp() mock_repo.git = MagicMock() async def rb_side(kind='file', fn=None, **kwargs): if kind == 'git' and fn == plugin.git_service.get_repo: return mock_repo return None mock_run_blocking.side_effect = rb_side with patch('src.plugins.git_plugin.SupersetClient') as MockSupersetClient: mock_client = MagicMock() MockSupersetClient.return_value = mock_client mock_client.authenticate = AsyncMock() mock_client.export_dashboard = AsyncMock(return_value=(b"zip", "ts")) result = await plugin._handle_sync(1, log=log, git_log=log, superset_log=log) assert result["status"] == "success" class TestHandleDeploy: """Verify _handle_deploy method.""" @pytest.mark.asyncio async def test_handle_deploy_success(self): """Happy: full deploy flow succeeds.""" plugin, mock_cm = _make_plugin() log = _mock_log() mock_env = MagicMock() mock_env.name = "Target Env" mock_cm.get_environment.return_value = mock_env with patch('src.plugins.git_plugin.run_blocking') as mock_run_blocking: mock_repo = MagicMock() mock_repo.working_dir = tempfile.mkdtemp() mock_repo.git = MagicMock() async def rb_side(kind='file', fn=None, **kwargs): if kind == 'git' and fn == plugin.git_service.get_repo: return mock_repo if fn.__name__ == '_pack_deploy_zip': return None if fn == Path.write_bytes: return None if fn == Path.exists: return True if fn == os.remove: return None return None mock_run_blocking.side_effect = rb_side with patch('src.plugins.git_plugin.SupersetClient') as MockSupersetClient: mock_client = MagicMock() MockSupersetClient.return_value = mock_client mock_client.authenticate = AsyncMock() mock_client.import_dashboard = AsyncMock(return_value={"import": "ok"}) result = await plugin._handle_deploy(1, "env-1", log=log, git_log=log, superset_log=log) assert result["status"] == "success" assert "deployed" in result["message"].lower() @pytest.mark.asyncio async def test_handle_deploy_missing_env_id(self): """Negative: empty env_id raises ValueError.""" plugin, _ = _make_plugin() log = _mock_log() with pytest.raises(ValueError, match="Target environment ID required"): await plugin._handle_deploy(1, None, log=log, git_log=log, superset_log=log) @pytest.mark.asyncio async def test_handle_deploy_env_not_found(self): """Negative: env_id not in config manager.""" plugin, mock_cm = _make_plugin() log = _mock_log() mock_cm.get_environment.return_value = None with patch('src.plugins.git_plugin.run_blocking') as mock_run_blocking: mock_repo = MagicMock() mock_repo.working_dir = tempfile.mkdtemp() async def rb_side(kind='file', fn=None, **kwargs): if kind == 'git' and fn == plugin.git_service.get_repo: return mock_repo return None mock_run_blocking.side_effect = rb_side with pytest.raises(ValueError, match="not found"): await plugin._handle_deploy(1, "nonexistent-env", log=log, git_log=log, superset_log=log) class TestGetEnv: """Verify _get_env method — all paths.""" def test_get_env_from_config(self): """Happy: get env from ConfigManager.""" plugin, mock_cm = _make_plugin() mock_env = MagicMock() mock_env.name = "Test Env" mock_env.id = "env-1" mock_cm.get_environment.return_value = mock_env result = plugin._get_env("env-1") assert result.name == "Test Env" def test_get_env_not_found_raises(self): """Negative: env not found in config or DB when env_id given.""" from src.plugins.git_plugin import GitPlugin as RealGitPlugin # We need a fresh plugin with properly mocked dependencies mock_cm = MagicMock() mock_cm.get_environment.return_value = None with patch('src.dependencies.config_manager', mock_cm): plugin = RealGitPlugin() with patch('src.core.database.SessionLocal') as MockSession: mock_db = MagicMock() MockSession.return_value = mock_db # DB query returns None too db_filter = MagicMock() db_filter.first.return_value = None mock_db.query.return_value.filter.return_value = db_filter with pytest.raises(ValueError, match="not found"): plugin._get_env("bad-env") def test_get_env_db_fallback(self): """Happy: get env from DB when not in config.""" plugin, mock_cm = _make_plugin() mock_cm.get_environment.return_value = None with patch('src.core.database.SessionLocal') as MockSession: mock_db = MagicMock() MockSession.return_value = mock_db mock_db_env = MagicMock() mock_db_env.id = "db-env-1" mock_db_env.name = "DB Env" mock_db_env.superset_url = "https://superset.example.com" mock_db_env.superset_token = "token123" db_filter = MagicMock() db_filter.first.return_value = mock_db_env mock_db.query.return_value.filter.return_value = db_filter with patch('src.core.config_models.Environment') as MockEnv: MockEnv.return_value.name = "DB Env" result = plugin._get_env("db-env-1") assert result.name == "DB Env" def test_get_env_first_active_db(self): """Happy: get first active env from DB when no env_id.""" plugin, mock_cm = _make_plugin() mock_cm.get_environment.return_value = None with patch('src.core.database.SessionLocal') as MockSession: mock_db = MagicMock() MockSession.return_value = mock_db mock_db_env = MagicMock() mock_db_env.id = "active-1" mock_db_env.name = "Active Env" mock_db_env.superset_url = "https://example.com" mock_db_env.superset_token = "token" # First filter (is_active) returns the mock # Second filter (first env) never reached db_filter = MagicMock() db_filter.first.return_value = mock_db_env mock_db.query.return_value.filter.return_value = db_filter with patch('src.core.config_models.Environment'): result = plugin._get_env(None) assert result is not None def test_get_env_fallback_to_first_config(self): """Happy: fallback to first env from config when no env_id and no DB env.""" plugin, mock_cm = _make_plugin() mock_cm.get_environment.return_value = None mock_env_list = [MagicMock(), MagicMock()] mock_env_list[0].name = "First Env" mock_cm.get_environments.return_value = mock_env_list with patch('src.core.database.SessionLocal') as MockSession: mock_db = MagicMock() MockSession.return_value = mock_db db_filter = MagicMock() db_filter.first.side_effect = [None, None] # no active, no first mock_db.query.return_value.filter.return_value = db_filter mock_db.query.return_value.first.return_value = None # plain .first() returns None result = plugin._get_env(None) assert result.name == "First Env" def test_get_env_no_envs_configured(self): """Negative: no environments at all raises ValueError.""" plugin, mock_cm = _make_plugin() mock_cm.get_environment.return_value = None mock_cm.get_environments.return_value = [] with patch('src.core.database.SessionLocal') as MockSession: mock_db = MagicMock() MockSession.return_value = mock_db db_filter = MagicMock() db_filter.first.side_effect = [None, None] mock_db.query.return_value.filter.return_value = db_filter mock_db.query.return_value.first.return_value = None # plain .first() returns None with pytest.raises(ValueError, match="No environments configured"): plugin._get_env(None) def test_get_env_cm_config_id_present(self): """Edge: env_id given but not found in config, but found in DB.""" plugin, mock_cm = _make_plugin() mock_cm.get_environment.return_value = None with patch('src.core.database.SessionLocal') as MockSession: mock_db = MagicMock() MockSession.return_value = mock_db mock_db_env = MagicMock() mock_db_env.id = "db-1" mock_db_env.name = "DB Found" mock_db_env.superset_url = "https://example.com" mock_db_env.superset_token = "tok" db_filter = MagicMock() db_filter.first.return_value = mock_db_env mock_db.query.return_value.filter.return_value = db_filter with patch('src.core.config_models.Environment') as MockEnv: MockEnv.return_value.name = "DB Found" result = plugin._get_env("db-1") assert result.name == "DB Found" # #endregion Test.GitPlugin