chore: update backend tests

This commit is contained in:
2026-06-16 12:01:03 +03:00
parent 6162fc824f
commit 508377f7a7
16 changed files with 149 additions and 89 deletions

View File

@@ -1,5 +1,10 @@
# ... (same header as before)
# #region Test.Api.Tasks [C:3] [TYPE Module] [SEMANTICS test,tasks,api,routes,crud]
# @BRIEF API contract tests for task routes — create, list, get, update, cancel, and provider binding.
# @RELATION BINDS_TO -> [TasksApi]
# @TEST_EDGE: missing_plugin_id -> 400 when plugin_id missing
# @TEST_EDGE: invalid_method -> 405 for unsupported HTTP method
# @TEST_EDGE: task_not_found -> 404 for nonexistent task
# @TEST_EDGE: unauthorized_access -> 401 when no auth token
import os
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")

View File

@@ -1,3 +1,11 @@
# #region Test.Core.DefensiveGuards [C:3] [TYPE Module] [SEMANTICS test,defensive,guards,git,superset]
# @BRIEF Tests verifying defensive null-guards and error handling in GitService and SupersetClient.
# @RELATION BINDS_TO -> [GitService]
# @RELATION BINDS_TO -> [SupersetClient]
# @TEST_EDGE: null_dashboard_id -> ValueError raised for None dashboard_id
# @TEST_EDGE: null_file_name -> ValueError raised for None file_name
# @TEST_EDGE: missing_base_dir -> Directory recreated on access
# @TEST_EDGE: invalid_git_repo -> Recloned when path is not a valid repo
from pathlib import Path
import pytest
import shutil
@@ -151,3 +159,4 @@ async def test_git_service_configure_identity_updates_repo_local_config():
config_writer.set_value.assert_any_call("user", "name", "user_1")
config_writer.set_value.assert_any_call("user", "email", "user1@mail.ru")
# #endregion test_git_service_configure_identity_updates_repo_local_config
# #endregion Test.Core.DefensiveGuards

View File

@@ -1,5 +1,9 @@
# #region Test.DatasetReview.HelpersEdge [C:2] [TYPE Module] [SEMANTICS test,dataset,helpers,execution,snapshot,edge]
# #region Test.DatasetReview.HelpersEdge [C:3] [TYPE Module] [SEMANTICS test,dataset,helpers,execution,snapshot,edge]
# @BRIEF Edge-case coverage for orchestrator_pkg/_helpers.py uncovered lines.
# @RELATION BINDS_TO -> [DatasetReview.Helpers]
# @TEST_EDGE: missing_snapshot -> Handles None/empty snapshots
# @TEST_EDGE: invalid_type -> Wrong type in execution params
# @TEST_EDGE: execution_timeout -> Timeout handled during stage execution
from pathlib import Path
import sys

View File

@@ -1,5 +1,9 @@
# #region Test.DatasetReview.SemanticResolverEdge [C:2] [TYPE Module] [SEMANTICS test,semantic,resolver,fuzzy,match,edge]
# #region Test.DatasetReview.SemanticResolverEdge [C:3] [TYPE Module] [SEMANTICS test,semantic,resolver,fuzzy,match,edge]
# @BRIEF Edge-case coverage for semantic_resolver.py uncovered lines (149, 343, 347).
# @RELATION BINDS_TO -> [SemanticResolver]
# @TEST_EDGE: missing_mapping -> Returns empty when no mapping found
# @TEST_EDGE: invalid_type -> Non-string input handled gracefully
# @TEST_EDGE: empty_input -> Empty string returns no matches
from pathlib import Path
import sys

View File

@@ -1,9 +1,10 @@
# #region SupersetCompatibilityMatrixTests [C:2] [TYPE Module]
# @SEMANTICS: dataset_review, superset, compatibility_matrix, preview, sql_lab, tests
# @PURPOSE: Verifies Superset preview and SQL Lab endpoint fallback strategy used by dataset-review orchestration.
# @LAYER Tests
# @RELATION DEPENDS_ON ->[SupersetClient]
# @RELATION DEPENDS_ON ->[SupersetCompilationAdapter]
# #region Test.SupersetCompatibilityMatrix [C:3] [TYPE Module] [SEMANTICS test,dataset_review,superset,compatibility_matrix,preview,sql_lab]
# @BRIEF Verifies Superset preview and SQL Lab endpoint fallback strategy used by dataset-review orchestration.
# @RELATION BINDS_TO -> [SupersetClient]
# @RELATION BINDS_TO -> [SupersetCompilationAdapter]
# @TEST_EDGE: preview_unavailable -> Falls back to SQL Lab endpoint
# @TEST_EDGE: invalid_table_name -> Handles nonexistent table
# @TEST_EDGE: api_timeout -> Timeout during Superset API call
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock

View File

@@ -1,5 +1,9 @@
# #region Test.Core.ConnectionService [C:3] [TYPE Module] [SEMANTICS test, connections, service, settings]
# @BRIEF Pytest tests for ConnectionService CRUD, encryption, test, and deletion blocking.
# @RELATION BINDS_TO -> [ConnectionService]
# @TEST_EDGE: missing_field -> ValueError for empty name
# @TEST_EDGE: invalid_type -> ValueError for unsupported dialect
# @TEST_EDGE: external_fail -> Connection error handled gracefully
# @TEST_CONTRACT ConnectionService CRUD operations reflect in GlobalSettings.connections.
# @TEST_CONTRACT Passwords are encrypted at rest (stored ≠ plaintext).
# @TEST_CONTRACT Test connection returns success/failure with diagnostics.

View File

@@ -1,6 +1,9 @@
# #region Test.DatasetsMixin [C:3] [TYPE Module] [SEMANTICS test, dataset, superset, crud, detail]
# @BRIEF Tests for core/superset_client/_datasets.py — SupersetDatasetsMixin.
# @RELATION BINDS_TO -> [SupersetDatasetsMixin]
# @TEST_EDGE: missing_param -> Null query param handled
# @TEST_EDGE: empty_result -> Empty dataset list returns 0 results
# @TEST_EDGE: external_api_error -> Exception in API call caught and logged
from pathlib import Path
import sys

View File

@@ -1,6 +1,9 @@
# #region Test.PluginBase [C:3] [TYPE Module] [SEMANTICS test,plugin,base,abstract]
# @BRIEF Tests for core/plugin_base.py — PluginBase, PluginConfig.
# @RELATION BINDS_TO -> [PluginBase]
# @TEST_EDGE: missing_required_property -> TypeError on abstract instantiation
# @TEST_EDGE: invalid_plugin_config -> ValidationError on missing fields
# @TEST_EDGE: abstract_method_call -> super().abstract_method returns None
from pathlib import Path
import sys

View File

@@ -1,5 +1,9 @@
# #region Test.AppTimezoneEdge [C:1] [TYPE Module] [SEMANTICS test,timezone,format,offset,edge]
# #region Test.AppTimezoneEdge [C:3] [TYPE Module] [SEMANTICS test,timezone,format,offset,edge]
# @BRIEF Edge-case coverage for core/timezone.py — format_timezone_offset fallback.
# @RELATION BINDS_TO -> [Timezone]
# @TEST_EDGE: empty_strftime -> Fallback to '+00:00' when strftime returns empty
# @TEST_EDGE: invalid_timezone -> validate_timezone rejects invalid tz names
# @TEST_EDGE: missing_env_var -> Default timezone when env var not set
from datetime import timezone, timedelta
from unittest.mock import patch

View File

@@ -1,8 +1,11 @@
# #region TestDatasetsApi [C:2] [TYPE Module]
# @RELATION DEPENDS_ON -> [DatasetsApi]
# @SEMANTICS: tests, datasets, api, metrics, inline-edit
# @PURPOSE: Contract tests for dataset endpoints (list+stats, detail+metrics, inline-edit descriptions).
# @LAYER Tests
# #region Test.DatasetsApi [C:3] [TYPE Module] [SEMANTICS test, datasets, api, metrics, inline-edit]
# @BRIEF Contract tests for dataset endpoints — list+stats, detail+metrics, inline-edit descriptions, map-columns, generate-docs.
# @RELATION BINDS_TO -> [DatasetsApi]
# @TEST_EDGE: missing_env -> 404 when env not found
# @TEST_EDGE: invalid_page -> 400 when page < 1 or page_size > 100
# @TEST_EDGE: external_superset_fail -> 503 when Superset/API fails
# @TEST_EDGE: html_injection -> HTML tags stripped from descriptions
# @TEST_EDGE: oversized_description -> 400 when description exceeds 2000 chars
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
@@ -20,7 +23,7 @@ from src.dependencies import (
client = TestClient(app)
# #region mock_user [C:2] [TYPE Function]
# @PURPOSE: Create a mock user with Admin role and all permissions for testing.
# @BRIEF Create a mock user with Admin role and all permissions for testing.
def _make_mock_user():
admin_role = MagicMock()
admin_role.name = "Admin"
@@ -33,7 +36,7 @@ def _make_mock_user():
# #region mock_deps [C:2] [TYPE Function]
# @PURPOSE: Provide dependency override fixture for dataset route tests.
# @BRIEF Provide dependency override fixture for dataset route tests.
# @TEST_FIXTURE: dataset_route_overrides -> INLINE_JSON
@pytest.fixture
def mock_deps():
@@ -98,7 +101,7 @@ def mock_deps():
# #region test_get_datasets_returns_stats [C:2] [TYPE Function]
# @PURPOSE: Verify GET /api/datasets returns stats object with correct counts.
# @BRIEF Verify GET /api/datasets returns stats object with correct counts.
# @TEST_CONTRACT: datasets_list -> datasets with stats payload
# @TEST_SCENARIO: datasets_with_stats -> HTTP 200 returns datasets, stats, and pagination.
# @TEST_INVARIANT: stats_counts_correct -> VERIFIED_BY: [datasets_with_stats]
@@ -120,7 +123,7 @@ def test_get_datasets_returns_stats(mock_deps):
# #region test_get_datasets_filter_unmapped [C:2] [TYPE Function]
# @PURPOSE: Verify server-side filtering by unmapped datasets.
# @BRIEF Verify server-side filtering by unmapped datasets.
# @TEST_SCENARIO: filter_unmapped -> Only returns datasets with mapped=0.
def test_get_datasets_filter_unmapped(mock_deps):
response = client.get(
@@ -137,7 +140,7 @@ def test_get_datasets_filter_unmapped(mock_deps):
# #region test_get_datasets_filter_mapped [C:2] [TYPE Function]
# @PURPOSE: Verify server-side filtering by mapped datasets.
# @BRIEF Verify server-side filtering by mapped datasets.
def test_get_datasets_filter_mapped(mock_deps):
response = client.get(
"/api/datasets?env_id=ss-dev&filter=mapped&page=1&page_size=10"
@@ -150,7 +153,7 @@ def test_get_datasets_filter_mapped(mock_deps):
# #region test_get_datasets_filter_linked [C:2] [TYPE Function]
# @PURPOSE: Verify server-side filtering by datasets linked to dashboards.
# @BRIEF Verify server-side filtering by datasets linked to dashboards.
def test_get_datasets_filter_linked(mock_deps):
response = client.get(
"/api/datasets?env_id=ss-dev&filter=linked&page=1&page_size=10"
@@ -163,7 +166,7 @@ def test_get_datasets_filter_linked(mock_deps):
# #region test_get_datasets_filter_all [C:2] [TYPE Function]
# @PURPOSE: Verify filter=all returns all datasets (same as no filter).
# @BRIEF Verify filter=all returns all datasets (same as no filter).
def test_get_datasets_filter_all(mock_deps):
response = client.get(
"/api/datasets?env_id=ss-dev&filter=all&page=1&page_size=10"
@@ -174,7 +177,7 @@ def test_get_datasets_filter_all(mock_deps):
# #region test_get_dataset_detail_returns_metrics [C:2] [TYPE Function]
# @PURPOSE: Verify GET /api/datasets/{id} returns metrics and metric_count.
# @BRIEF Verify GET /api/datasets/{id} returns metrics and metric_count.
# @TEST_SCENARIO: detail_with_metrics -> Response includes metrics list and metric_count.
def test_get_dataset_detail_returns_metrics(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
@@ -218,7 +221,7 @@ def test_get_dataset_detail_returns_metrics(mock_deps):
# #region test_update_column_description [C:2] [TYPE Function]
# @PURPOSE: Verify PUT /api/datasets/{id}/columns/{col_id}/description saves description.
# @BRIEF Verify PUT /api/datasets/{id}/columns/{col_id}/description saves description.
# @TEST_SCENARIO: save_column_desc -> Saves and returns updated description.
def test_update_column_description(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
@@ -255,7 +258,7 @@ def test_update_column_description(mock_deps):
# #region test_update_metric_description [C:2] [TYPE Function]
# @PURPOSE: Verify PUT /api/datasets/{id}/metrics/{metric_id}/description saves correctly.
# @BRIEF Verify PUT /api/datasets/{id}/metrics/{metric_id}/description saves correctly.
# @TEST_SCENARIO: save_metric_desc -> Saves and returns updated description.
def test_update_metric_description(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
@@ -285,7 +288,7 @@ def test_update_metric_description(mock_deps):
# #region test_update_column_description_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when column not found.
# @BRIEF Verify 404 when column not found.
def test_update_column_description_not_found(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -308,7 +311,7 @@ def test_update_column_description_not_found(mock_deps):
# #region test_update_column_description_validation [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when description exceeds max length.
# @BRIEF Verify 400 when description exceeds max length.
def test_update_column_description_validation(mock_deps):
response = client.put(
"/api/datasets/1/columns/10/description?env_id=ss-dev",
@@ -318,7 +321,7 @@ def test_update_column_description_validation(mock_deps):
# #region test_get_datasets_superset_503 [C:2] [TYPE Function]
# @PURPOSE: Verify GET /api/datasets returns 503 when Superset/ResourceService throws.
# @BRIEF Verify GET /api/datasets returns 503 when Superset/ResourceService throws.
# @TEST_SCENARIO: upstream_superset_failure -> 503 with error detail.
# @TEST_INVARIANT: upstream_failure_503 -> VERIFIED_BY: [superset_failure]
def test_get_datasets_superset_503(mock_deps):
@@ -343,7 +346,7 @@ def test_get_datasets_superset_503(mock_deps):
# #region test_update_column_description_superset_502 [C:2] [TYPE Function]
# @PURPOSE: Verify PUT column description returns 502 when SupersetClient.update_dataset throws.
# @BRIEF Verify PUT column description returns 502 when SupersetClient.update_dataset throws.
# @TEST_SCENARIO: superset_put_failure -> 502 with error detail.
def test_update_column_description_superset_502(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
@@ -372,7 +375,7 @@ def test_update_column_description_superset_502(mock_deps):
# #region test_update_column_description_strips_html [C:2] [TYPE Function]
# @PURPOSE: Verify HTML tags are stripped from description before saving.
# @BRIEF Verify HTML tags are stripped from description before saving.
# @TEST_SCENARIO: html_in_description -> HTML tags stripped, clean text saved.
def test_update_column_description_strips_html(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
@@ -407,7 +410,7 @@ def test_update_column_description_strips_html(mock_deps):
# #region test_dataset_item_has_metric_count [C:2] [TYPE Function]
# @PURPOSE: Verify DatasetItem in list response includes metric_count field.
# @BRIEF Verify DatasetItem in list response includes metric_count field.
def test_dataset_item_has_metric_count(mock_deps):
response = client.get(
"/api/datasets?env_id=ss-dev&page=1&page_size=10"
@@ -420,7 +423,7 @@ def test_dataset_item_has_metric_count(mock_deps):
# #region test_get_dataset_ids [C:2] [TYPE Function]
# @PURPOSE: Verify GET /api/datasets/ids returns list of dataset IDs.
# @BRIEF Verify GET /api/datasets/ids returns list of dataset IDs.
def test_get_dataset_ids(mock_deps):
response = client.get("/api/datasets/ids?env_id=ss-dev")
assert response.status_code == 200
@@ -432,7 +435,7 @@ def test_get_dataset_ids(mock_deps):
# #region test_get_dataset_ids_search [C:2] [TYPE Function]
# @PURPOSE: Verify GET /api/datasets/ids with search filter.
# @BRIEF Verify GET /api/datasets/ids with search filter.
def test_get_dataset_ids_search(mock_deps):
response = client.get("/api/datasets/ids?env_id=ss-dev&search=users")
assert response.status_code == 200
@@ -441,14 +444,14 @@ def test_get_dataset_ids_search(mock_deps):
# #region test_get_dataset_ids_env_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when env not found.
# @BRIEF Verify 404 when env not found.
def test_get_dataset_ids_env_not_found(mock_deps):
response = client.get("/api/datasets/ids?env_id=nonexistent")
assert response.status_code == 404
# #region test_get_dataset_ids_503 [C:2] [TYPE Function]
# @PURPOSE: Verify 503 when Superset fails.
# @BRIEF Verify 503 when Superset fails.
def test_get_dataset_ids_503(mock_deps):
from src.dependencies import get_resource_service
failing = MagicMock()
@@ -464,28 +467,28 @@ def test_get_dataset_ids_503(mock_deps):
# #region test_get_datasets_invalid_page [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when page < 1.
# @BRIEF Verify 400 when page < 1.
def test_get_datasets_invalid_page(mock_deps):
response = client.get("/api/datasets?env_id=ss-dev&page=0")
assert response.status_code == 400
# #region test_get_datasets_invalid_page_size [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when page_size > 100.
# @BRIEF Verify 400 when page_size > 100.
def test_get_datasets_invalid_page_size(mock_deps):
response = client.get("/api/datasets?env_id=ss-dev&page_size=200")
assert response.status_code == 400
# #region test_get_datasets_env_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when environment not found.
# @BRIEF Verify 404 when environment not found.
def test_get_datasets_env_not_found(mock_deps):
response = client.get("/api/datasets?env_id=nonexistent&page=1&page_size=10")
assert response.status_code == 404
# #region test_get_datasets_search_filter [C:2] [TYPE Function]
# @PURPOSE: Verify search filter narrows results.
# @BRIEF Verify search filter narrows results.
def test_get_datasets_search_filter(mock_deps):
response = client.get("/api/datasets?env_id=ss-dev&search=users&page=1&page_size=10")
assert response.status_code == 200
@@ -495,7 +498,7 @@ def test_get_datasets_search_filter(mock_deps):
# #region test_get_datasets_pagination [C:2] [TYPE Function]
# @PURPOSE: Verify pagination works.
# @BRIEF Verify pagination works.
def test_get_datasets_pagination(mock_deps):
response = client.get("/api/datasets?env_id=ss-dev&page=1&page_size=2")
assert response.status_code == 200
@@ -505,7 +508,7 @@ def test_get_datasets_pagination(mock_deps):
# #region test_map_columns [C:2] [TYPE Function]
# @PURPOSE: Verify POST /api/datasets/map-columns creates mapping task.
# @BRIEF Verify POST /api/datasets/map-columns creates mapping task.
def test_map_columns(mock_deps):
from src.dependencies import get_task_manager
task_manager_mock = MagicMock()
@@ -542,7 +545,7 @@ def test_map_columns(mock_deps):
# #region test_map_columns_no_dataset_ids [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when no dataset IDs provided.
# @BRIEF Verify 400 when no dataset IDs provided.
def test_map_columns_no_dataset_ids(mock_deps):
from src.dependencies import has_permission
app.dependency_overrides[has_permission("plugin:mapper", "EXECUTE")] = lambda: True
@@ -558,7 +561,7 @@ def test_map_columns_no_dataset_ids(mock_deps):
# #region test_map_columns_invalid_source_type [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when invalid source type.
# @BRIEF Verify 400 when invalid source type.
def test_map_columns_invalid_source_type(mock_deps):
from src.dependencies import has_permission
app.dependency_overrides[has_permission("plugin:mapper", "EXECUTE")] = lambda: True
@@ -574,7 +577,7 @@ def test_map_columns_invalid_source_type(mock_deps):
# #region test_map_columns_missing_database_id [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when sqllab source without database_id.
# @BRIEF Verify 400 when sqllab source without database_id.
def test_map_columns_missing_database_id(mock_deps):
from src.dependencies import has_permission
app.dependency_overrides[has_permission("plugin:mapper", "EXECUTE")] = lambda: True
@@ -590,7 +593,7 @@ def test_map_columns_missing_database_id(mock_deps):
# #region test_map_columns_env_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when environment not found.
# @BRIEF Verify 404 when environment not found.
def test_map_columns_env_not_found(mock_deps):
from src.dependencies import has_permission
app.dependency_overrides[has_permission("plugin:mapper", "EXECUTE")] = lambda: True
@@ -611,7 +614,7 @@ def test_map_columns_env_not_found(mock_deps):
# #region test_map_columns_xlsx_source [C:2] [TYPE Function]
# @PURPOSE: Verify xlsx source type works.
# @BRIEF Verify xlsx source type works.
def test_map_columns_xlsx_source(mock_deps):
from src.dependencies import get_task_manager, has_permission
task_manager_mock = MagicMock()
@@ -640,7 +643,7 @@ def test_map_columns_xlsx_source(mock_deps):
# #region test_map_columns_503 [C:2] [TYPE Function]
# @PURPOSE: Verify 503 when task creation fails.
# @BRIEF Verify 503 when task creation fails.
def test_map_columns_503(mock_deps):
from src.dependencies import get_task_manager, has_permission
task_manager_mock = MagicMock()
@@ -666,7 +669,7 @@ def test_map_columns_503(mock_deps):
# #region test_generate_docs [C:2] [TYPE Function]
# @PURPOSE: Verify POST /api/datasets/generate-docs creates doc generation task.
# @BRIEF Verify POST /api/datasets/generate-docs creates doc generation task.
def test_generate_docs(mock_deps):
from src.dependencies import get_task_manager, has_permission
task_manager_mock = MagicMock()
@@ -695,7 +698,7 @@ def test_generate_docs(mock_deps):
# #region test_generate_docs_no_dataset_ids [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when no dataset IDs provided.
# @BRIEF Verify 400 when no dataset IDs provided.
def test_generate_docs_no_dataset_ids(mock_deps):
from src.dependencies import has_permission
app.dependency_overrides[has_permission("plugin:llm_analysis", "EXECUTE")] = lambda: True
@@ -711,7 +714,7 @@ def test_generate_docs_no_dataset_ids(mock_deps):
# #region test_generate_docs_env_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when environment not found.
# @BRIEF Verify 404 when environment not found.
def test_generate_docs_env_not_found(mock_deps):
from src.dependencies import has_permission
app.dependency_overrides[has_permission("plugin:llm_analysis", "EXECUTE")] = lambda: True
@@ -727,7 +730,7 @@ def test_generate_docs_env_not_found(mock_deps):
# #region test_generate_docs_503 [C:2] [TYPE Function]
# @PURPOSE: Verify 503 when task creation fails.
# @BRIEF Verify 503 when task creation fails.
def test_generate_docs_503(mock_deps):
from src.dependencies import get_task_manager, has_permission
task_manager_mock = MagicMock()
@@ -748,7 +751,7 @@ def test_generate_docs_503(mock_deps):
# #region test_update_metric_description_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when metric not found.
# @BRIEF Verify 404 when metric not found.
def test_update_metric_description_not_found(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -771,7 +774,7 @@ def test_update_metric_description_not_found(mock_deps):
# #region test_update_metric_description_validation [C:2] [TYPE Function]
# @PURPOSE: Verify 400 when description exceeds max length for metric.
# @BRIEF Verify 400 when description exceeds max length for metric.
def test_update_metric_description_validation(mock_deps):
response = client.put(
"/api/datasets/1/metrics/100/description?env_id=ss-dev",
@@ -781,7 +784,7 @@ def test_update_metric_description_validation(mock_deps):
# #region test_update_metric_description_superset_502_get [C:2] [TYPE Function]
# @PURPOSE: Verify 502 when Superset GET fails for metric.
# @BRIEF Verify 502 when Superset GET fails for metric.
def test_update_metric_description_superset_502_get(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -796,7 +799,7 @@ def test_update_metric_description_superset_502_get(mock_deps):
# #region test_update_metric_description_superset_502_put [C:2] [TYPE Function]
# @PURPOSE: Verify 502 when Superset PUT fails for metric.
# @BRIEF Verify 502 when Superset PUT fails for metric.
def test_update_metric_description_superset_502_put(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -819,7 +822,7 @@ def test_update_metric_description_superset_502_put(mock_deps):
# #region test_update_column_description_env_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when environment not found in update_column_description.
# @BRIEF Verify 404 when environment not found in update_column_description.
def test_update_column_description_env_not_found(mock_deps):
response = client.put(
"/api/datasets/1/columns/10/description?env_id=nonexistent",
@@ -829,7 +832,7 @@ def test_update_column_description_env_not_found(mock_deps):
# #region test_update_column_description_get_fails [C:2] [TYPE Function]
# @PURPOSE: Verify 502 when Superset GET fails in update_column_description.
# @BRIEF Verify 502 when Superset GET fails in update_column_description.
def test_update_column_description_get_fails(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -844,7 +847,7 @@ def test_update_column_description_get_fails(mock_deps):
# #region test_update_column_description_response_without_result_key [C:2] [TYPE Function]
# @PURPOSE: Cover line 562: elif branch when response has no "result" key.
# @BRIEF Cover line 562: elif branch when response has no "result" key.
def test_update_column_description_response_without_result_key(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -861,7 +864,7 @@ def test_update_column_description_response_without_result_key(mock_deps):
# #region test_update_column_description_dataset_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when dataset has no id after GET.
# @BRIEF Verify 404 when dataset has no id after GET.
def test_update_column_description_dataset_not_found(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -876,7 +879,7 @@ def test_update_column_description_dataset_not_found(mock_deps):
# #region test_update_column_description_unexpected_error [C:2] [TYPE Function]
# @PURPOSE: Verify 500 on unexpected error.
# @BRIEF Verify 500 on unexpected error.
def test_update_column_description_unexpected_error(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -891,7 +894,7 @@ def test_update_column_description_unexpected_error(mock_deps):
# #region test_update_metric_description_env_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when env not found for metric update.
# @BRIEF Verify 404 when env not found for metric update.
def test_update_metric_description_env_not_found(mock_deps):
response = client.put(
"/api/datasets/1/metrics/100/description?env_id=nonexistent",
@@ -901,7 +904,7 @@ def test_update_metric_description_env_not_found(mock_deps):
# #region test_update_metric_description_no_result_key [C:2] [TYPE Function]
# @PURPOSE: Cover line 650: elif branch when response has no "result" key for metric.
# @BRIEF Cover line 650: elif branch when response has no "result" key for metric.
def test_update_metric_description_no_result_key(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -917,7 +920,7 @@ def test_update_metric_description_no_result_key(mock_deps):
# #region test_update_metric_description_dataset_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when dataset not found for metric update.
# @BRIEF Verify 404 when dataset not found for metric update.
def test_update_metric_description_dataset_not_found(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -932,7 +935,7 @@ def test_update_metric_description_dataset_not_found(mock_deps):
# #region test_update_metric_description_unexpected_error [C:2] [TYPE Function]
# @PURPOSE: Verify 500 on unexpected error for metric update.
# @BRIEF Verify 500 on unexpected error for metric update.
def test_update_metric_description_unexpected_error(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -947,7 +950,7 @@ def test_update_metric_description_unexpected_error(mock_deps):
# #region test_update_column_description_type_error [C:2] [TYPE Function]
# @PURPOSE: Verify 500 on TypeError in update_column_description (covers lines 597-599).
# @BRIEF Verify 500 on TypeError in update_column_description (covers lines 597-599).
def test_update_column_description_type_error(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -963,7 +966,7 @@ def test_update_column_description_type_error(mock_deps):
# #region test_update_metric_description_type_error [C:2] [TYPE Function]
# @PURPOSE: Verify 500 on TypeError in update_metric_description (covers lines 684-686).
# @BRIEF Verify 500 on TypeError in update_metric_description (covers lines 684-686).
def test_update_metric_description_type_error(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -978,7 +981,7 @@ def test_update_metric_description_type_error(mock_deps):
# #region test_get_dataset_detail_database_object [C:2] [TYPE Function]
# @PURPOSE: Verify database field normalization when it's a dict.
# @BRIEF Verify database field normalization when it's a dict.
def test_get_dataset_detail_database_object(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -1006,7 +1009,7 @@ def test_get_dataset_detail_database_object(mock_deps):
# #region test_get_dataset_detail_503 [C:2] [TYPE Function]
# @PURPOSE: Verify 503 when Superset fails for dataset detail.
# @BRIEF Verify 503 when Superset fails for dataset detail.
def test_get_dataset_detail_503(mock_deps):
with patch("src.api.routes.datasets.AsyncSupersetClient") as MockClient:
mock_client = AsyncMock()
@@ -1018,14 +1021,14 @@ def test_get_dataset_detail_503(mock_deps):
# #region test_get_dataset_detail_env_not_found [C:2] [TYPE Function]
# @PURPOSE: Verify 404 when env not found for dataset detail.
# @BRIEF Verify 404 when env not found for dataset detail.
def test_get_dataset_detail_env_not_found(mock_deps):
response = client.get("/api/datasets/1?env_id=nonexistent")
assert response.status_code == 404
# #region test_strip_html_tags_function [C:2] [TYPE Function]
# @PURPOSE: Test _strip_html_tags directly.
# @BRIEF Test _strip_html_tags directly.
def test_strip_html_tags_empty():
from src.api.routes.datasets import _strip_html_tags
assert _strip_html_tags("") == ""

View File

@@ -1,5 +1,9 @@
# #region Test.Core.DbExecutor [C:3] [TYPE Module] [SEMANTICS test, database, executor, direct]
# @BRIEF Pytest tests for DbExecutor — direct database INSERT execution.
# @RELATION BINDS_TO -> [DbExecutor]
# @TEST_EDGE: missing_connection -> Returns error "not found"
# @TEST_EDGE: invalid_type -> Unsupported dialect returns error
# @TEST_EDGE: external_fail -> Execution error handled gracefully
# @TEST_CONTRACT DbExecutor.execute_sql routes to correct dialect driver and returns DbExecutionResult.
# @TEST_CONTRACT Connection pool is reused across calls (same connection_id → same pool).
# AUDIT_NOTE: patch.object targets _execute_pg/_execute_ch/_execute_mysql which are

View File

@@ -1,12 +1,15 @@
# #region Test.Models.Environment [C:2] [TYPE Module] [SEMANTICS test,environment,config,model]
# @BRIEF Verify Environment config model stores and returns values correctly.
# @RELATION BINDS_TO -> [Environment]
# @TEST_EDGE: default_values -> Defaults are set correctly
# @TEST_EDGE: missing_field -> Raises ValidationError for missing required field
# @TEST_EDGE: invalid_type -> Raises ValidationError for wrong type
from src.core.config_models import Environment
from src.core.logger import belief_scope
# #region test_environment_model [C:2] [TYPE Function]
# @RELATION BINDS_TO -> Environment
# @PURPOSE: Tests that Environment model correctly stores values.
# @PRE: Environment class is available.
# @POST: Values are verified.
# @BRIEF Verify Environment model correctly stores and returns attribute values.
def test_environment_model():
with belief_scope("test_environment_model"):
env = Environment(
@@ -20,3 +23,4 @@ def test_environment_model():
assert env.name == "test-env"
assert env.url == "http://localhost:8088/api/v1"
# #endregion test_environment_model
# #endregion Test.Models.Environment

View File

@@ -1,5 +1,9 @@
# #region Test.Translate.OrchestratorSqlDirectDb [C:3] [TYPE Module] [SEMANTICS test, translate, orchestrator, direct-db]
# @BRIEF Pytest tests for SQLInsertService direct_db dispatch in orchestrator_sql.
# @RELATION BINDS_TO -> [SQLInsertService]
# @TEST_EDGE: missing_connection -> direct_db fails without connection_id
# @TEST_EDGE: invalid_insert_method -> Unsupported insert_method handled
# @TEST_EDGE: external_db_failure -> DB execution errors caught and reported
# @TEST_CONTRACT SQLInsertService dispatches to direct DB executor when job.insert_method == "direct_db".
# @TEST_CONTRACT Connection snapshot is stored on TranslationRun for direct DB inserts.

View File

@@ -1,5 +1,10 @@
# #region Test.Schemas.Edge [C:1] [TYPE Module] [SEMANTICS test,schemas,auth,translate,edge]
# #region Test.Schemas.Edge [C:3] [TYPE Module] [SEMANTICS test,schemas,auth,translate,edge]
# @BRIEF Edge-case coverage for schemas/auth.py and schemas/translate.py uncovered lines.
# @RELATION BINDS_TO -> [Schemas.Auth]
# @RELATION BINDS_TO -> [Schemas.Translate]
# @TEST_EDGE: missing_field -> Password missing digit/lowercase/uppercase
# @TEST_EDGE: invalid_type -> BCP-47 tag validation rejects malformed
# @TEST_EDGE: oversized_input -> Pattern exceeds 500 char limit
import pytest
from pydantic import ValidationError

View File

@@ -1,17 +1,11 @@
# #region TestSmokeApp [C:2] [TYPE Module]
# @SEMANTICS: tests, smoke, app, imports, fastapi
# @PURPOSE: Minimal smoke tests that verify the full application import chain
# succeeds without SyntaxError, IndentationError, ImportError, or
# NameError in any module.
# @LAYER Tests (Smoke)
# @RELATION BINDS_TO -> src/app.py
# @RELATION BINDS_TO -> src/core/cot_logger.py
# #region Test.SmokeApp [C:3] [TYPE Module] [SEMANTICS test, smoke, app, imports, fastapi]
# @BRIEF Minimal smoke tests verifying the full application import chain succeeds without errors.
# @RELATION BINDS_TO -> [App]
# @TEST_EDGE: import_failure -> ImportError/SyntaxError prevents app startup
# @TEST_EDGE: missing_middleware -> TraceContextMiddleware not registered
# @TEST_EDGE: missing_db_url -> App requires DATABASE_URL env var
# @INVARIANT: All tests must pass without a running PostgreSQL instance.
#
# @RATIONALE: Uses SQLite in-memory database URLs to avoid requiring a live
# PostgreSQL server during smoke testing. The database module's
# _build_engine() already supports SQLite with
# check_same_thread=False.
# @RATIONALE: Uses SQLite in-memory database URLs to avoid requiring a live PostgreSQL server during smoke testing.
"""Smoke test: verify the full application imports without errors.

View File

@@ -1,3 +1,11 @@
# #region Test.SmokePlugins [C:3] [TYPE Module] [SEMANTICS test,plugin,smoke,loader,integration]
# @BRIEF Smoke tests verifying plugin discovery, instantiation, and TaskManager integration.
# @RELATION BINDS_TO -> [PluginLoader]
# @RELATION BINDS_TO -> [TaskManager]
# @TEST_EDGE: missing_plugin -> Missing expected plugins detected
# @TEST_EDGE: plugin_import_error -> Plugin load fails gracefully
# @TEST_EDGE: task_manager_init_failure -> TaskManager initialization error handled
# @RATIONALE Smoke tests exercise the full plugin loading chain without external deps.
import os
from pathlib import Path
import pytest
@@ -86,3 +94,4 @@ class TestPluginSmoke:
manager._flusher_thread.join(timeout=2)
assert manager is not None
# #endregion Test.SmokePlugins