test: fix backend and frontend test contracts

This commit is contained in:
2026-07-14 10:43:38 +03:00
parent 66497da72b
commit 2a56ea5fc9
9 changed files with 44 additions and 12 deletions

View File

@@ -183,6 +183,7 @@ async def sync_dashboard(
"source_env_id": source_env_id, "source_env_id": source_env_id,
} }
) )
return result
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:

View File

@@ -69,6 +69,7 @@ class EventBus:
# @POST Flusher task is created and scheduled in the event loop. # @POST Flusher task is created and scheduled in the event loop.
def start(self) -> None: def start(self) -> None:
if self._flusher_task is None or self._flusher_task.done(): if self._flusher_task is None or self._flusher_task.done():
asyncio.get_running_loop()
self._flusher_task = asyncio.create_task(self.async_flusher_loop()) self._flusher_task = asyncio.create_task(self.async_flusher_loop())
# #endregion start # #endregion start

View File

@@ -133,7 +133,10 @@ class StoragePlugin(PluginBase):
# Use TaskContext logger if available, otherwise fall back to app logger # Use TaskContext logger if available, otherwise fall back to app logger
log = context.logger if context else logger log = context.logger if context else logger
logger.reason("Executing storage task", payload={"params": params}) if context is not None and hasattr(log, "with_source"):
log = log.with_source("StoragePlugin.execute")
log.reason("Executing storage task", payload={"params": params})
# endregion execute # endregion execute
# region get_storage_root [TYPE Function] # region get_storage_root [TYPE Function]

View File

@@ -273,6 +273,12 @@ class TestPromoteDashboard:
class TestDeployDashboard: class TestDeployDashboard:
"""POST /repositories/{dashboard_ref}/deploy""" """POST /repositories/{dashboard_ref}/deploy"""
@staticmethod
def _target_environment():
target = MagicMock()
target.id = "preprod-1"
return target
def test_success(self): def test_success(self):
mock_plugin = MagicMock() mock_plugin = MagicMock()
mock_plugin.execute = AsyncMock(return_value={"status": "deployed"}) mock_plugin.execute = AsyncMock(return_value={"status": "deployed"})
@@ -280,9 +286,10 @@ class TestDeployDashboard:
with ( with (
patch("src.plugins.git_plugin.GitPlugin", return_value=mock_plugin), patch("src.plugins.git_plugin.GitPlugin", return_value=mock_plugin),
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)), patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
patch("src.api.routes.git._repo_lifecycle_routes._resolve_stage_environment", return_value=self._target_environment()),
): ):
client = _make_client() client = _make_client()
resp = client.post("/repositories/42/deploy", json={"environment_id": "prod"}) resp = client.post("/repositories/42/deploy", json={"stage": "preprod"})
assert resp.status_code == 200 assert resp.status_code == 200
assert resp.json()["status"] == "deployed" assert resp.json()["status"] == "deployed"
@@ -293,9 +300,10 @@ class TestDeployDashboard:
with ( with (
patch("src.plugins.git_plugin.GitPlugin", return_value=mock_plugin), patch("src.plugins.git_plugin.GitPlugin", return_value=mock_plugin),
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)), patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(return_value=42)),
patch("src.api.routes.git._repo_lifecycle_routes._resolve_stage_environment", return_value=self._target_environment()),
): ):
client = _make_client() client = _make_client()
resp = client.post("/repositories/42/deploy", json={"environment_id": "prod"}) resp = client.post("/repositories/42/deploy", json={"stage": "preprod"})
assert resp.status_code == 500 assert resp.status_code == 500
def test_dashboard_not_found(self): def test_dashboard_not_found(self):
@@ -304,6 +312,6 @@ class TestDeployDashboard:
patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(side_effect=HTTPException(status_code=404, detail="Not found"))), patch("src.api.routes.git._resolve_dashboard_id_from_ref", AsyncMock(side_effect=HTTPException(status_code=404, detail="Not found"))),
): ):
client = _make_client() client = _make_client()
resp = client.post("/repositories/bad-ref/deploy", json={"environment_id": "prod"}) resp = client.post("/repositories/bad-ref/deploy", json={"stage": "prod"})
assert resp.status_code == 404 assert resp.status_code == 404
# #endregion Test.Api.GitRepoLifecycleRoutes # #endregion Test.Api.GitRepoLifecycleRoutes

View File

@@ -357,7 +357,7 @@ class TestGenerateCommitMessage:
def test_no_changes(self): def test_no_changes(self):
mock_gs = MagicMock() mock_gs = MagicMock()
mock_gs.get_diff = AsyncMock(return_value=[None, None]) mock_gs.get_diff = AsyncMock(return_value=None)
with ( with (
patch("src.api.routes.git._repo_operations_routes.get_git_service", return_value=mock_gs), patch("src.api.routes.git._repo_operations_routes.get_git_service", return_value=mock_gs),

View File

@@ -63,7 +63,7 @@ def _make_client(overrides: dict | None = None) -> TestClient:
class TestInitRepository: class TestInitRepository:
"""POST /repositories/{dashboard_ref}/init""" """POST /repositories/{dashboard_ref}/init"""
INIT_PAYLOAD = {"config_id": "cfg-1", "remote_url": "https://example.com/org/repo.git"} INIT_PAYLOAD = {"config_id": "cfg-1", "remote_url": "https://gitea.example.com/org/repo.git"}
def test_success_new_repo(self, mock_git_config): def test_success_new_repo(self, mock_git_config):
mock_db = MagicMock() mock_db = MagicMock()

View File

@@ -115,6 +115,7 @@ class TestDeleteFile:
def test_success(self): def test_success(self):
client, mock_loader = _make_client() client, mock_loader = _make_client()
mock_plugin = MagicMock() mock_plugin = MagicMock()
mock_plugin.delete_file = AsyncMock()
mock_loader.get_plugin.return_value = mock_plugin mock_loader.get_plugin.return_value = mock_plugin
resp = client.delete(f"{P}/files/backups/test.txt") resp = client.delete(f"{P}/files/backups/test.txt")
assert resp.status_code == 204 assert resp.status_code == 204

View File

@@ -105,12 +105,30 @@ def pytest_configure(config):
f"\n[conftest] SQLite (global: {_TEST_DB_PATH}) + FK enforcement", f"\n[conftest] SQLite (global: {_TEST_DB_PATH}) + FK enforcement",
file=sys.stderr, file=sys.stderr,
) )
if config.getoption("--run-integration"):
print(" Integration tests enabled (--run-integration).\n", file=sys.stderr)
else:
print( print(
" Integration tests skipped by default. Use --run-integration to enable.\n", " Integration tests skipped by default. Use --run-integration to enable.\n",
file=sys.stderr, file=sys.stderr,
) )
def pytest_collection_modifyitems(config, items):
"""Expose tests under tests/integration to the integration marker.
Integration tests are discovered by directory today, while pytest's
``-m integration`` selector operates on markers. Applying the marker at
collection time keeps both invocation styles equivalent:
``pytest tests/integration --run-integration`` and
``pytest -m integration --run-integration``.
"""
integration_marker = pytest.mark.integration
for item in items:
if "/tests/integration/" in f"/{item.nodeid}":
item.add_marker(integration_marker)
def pytest_unconfigure(config): def pytest_unconfigure(config):
try: try:
from src.core.database import engine as _global_engine from src.core.database import engine as _global_engine

View File

@@ -41,7 +41,7 @@ describe('GitFeatureWorkflow', () => {
branches: [{ name: 'feature/already-tested', commit_hash: 'merged-123456', ahead_of_dev: 0 }], branches: [{ name: 'feature/already-tested', commit_hash: 'merged-123456', ahead_of_dev: 0 }],
}); });
expect(screen.getByText(/Нет активных доработок/)).toBeTruthy(); expect(screen.getByText(/Нет отдельных доработок, ожидающих передачи в DEV/)).toBeTruthy();
expect(screen.getByText(/Черновики уже в DEV/)).toBeTruthy(); expect(screen.getByText(/Черновики уже в DEV/)).toBeTruthy();
expect(screen.queryByText('Передать в DEV')).toBeNull(); expect(screen.queryByText('Передать в DEV')).toBeNull();
}); });
@@ -52,7 +52,7 @@ describe('GitFeatureWorkflow', () => {
branches: [{ name: 'feature/remote-only', is_remote: true, ahead_of_dev: 5 }], branches: [{ name: 'feature/remote-only', is_remote: true, ahead_of_dev: 5 }],
}); });
expect(screen.getByText(/Нет отдельных черновиков/)).toBeTruthy(); expect(screen.getByText(/Нет отдельных доработок, ожидающих передачи в DEV/)).toBeTruthy();
expect(screen.queryByText('remote only')).toBeNull(); expect(screen.queryByText('remote only')).toBeNull();
}); });
}); });