From 508377f7a764ecb3b1272c4e35bb48a182551487 Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 16 Jun 2026 12:01:03 +0300 Subject: [PATCH] chore: update backend tests --- backend/tests/api/test_tasks.py | 9 +- backend/tests/core/test_defensive_guards.py | 9 ++ .../dataset_review/test_helpers_edge.py | 6 +- .../test_semantic_resolver_edge.py | 6 +- .../dataset_review/test_superset_matrix.py | 13 +- backend/tests/test_connection_service.py | 4 + backend/tests/test_core/test_datasets.py | 3 + backend/tests/test_core/test_plugin_base.py | 3 + backend/tests/test_core_timezone_edge.py | 6 +- backend/tests/test_datasets.py | 123 +++++++++--------- backend/tests/test_db_executor.py | 4 + backend/tests/test_models.py | 12 +- backend/tests/test_orchestrator_direct_db.py | 4 + backend/tests/test_schemas_edge.py | 7 +- backend/tests/test_smoke_app.py | 20 +-- backend/tests/test_smoke_plugins.py | 9 ++ 16 files changed, 149 insertions(+), 89 deletions(-) diff --git a/backend/tests/api/test_tasks.py b/backend/tests/api/test_tasks.py index 6738a6c6..8fe12899 100644 --- a/backend/tests/api/test_tasks.py +++ b/backend/tests/api/test_tasks.py @@ -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:") diff --git a/backend/tests/core/test_defensive_guards.py b/backend/tests/core/test_defensive_guards.py index e7233f79..2a2fd478 100644 --- a/backend/tests/core/test_defensive_guards.py +++ b/backend/tests/core/test_defensive_guards.py @@ -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 diff --git a/backend/tests/services/dataset_review/test_helpers_edge.py b/backend/tests/services/dataset_review/test_helpers_edge.py index febe1bfb..c153f036 100644 --- a/backend/tests/services/dataset_review/test_helpers_edge.py +++ b/backend/tests/services/dataset_review/test_helpers_edge.py @@ -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 diff --git a/backend/tests/services/dataset_review/test_semantic_resolver_edge.py b/backend/tests/services/dataset_review/test_semantic_resolver_edge.py index 39a42fa3..55a3daf1 100644 --- a/backend/tests/services/dataset_review/test_semantic_resolver_edge.py +++ b/backend/tests/services/dataset_review/test_semantic_resolver_edge.py @@ -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 diff --git a/backend/tests/services/dataset_review/test_superset_matrix.py b/backend/tests/services/dataset_review/test_superset_matrix.py index 30d7049a..1ec19e12 100644 --- a/backend/tests/services/dataset_review/test_superset_matrix.py +++ b/backend/tests/services/dataset_review/test_superset_matrix.py @@ -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 diff --git a/backend/tests/test_connection_service.py b/backend/tests/test_connection_service.py index c3b44165..31e000bb 100644 --- a/backend/tests/test_connection_service.py +++ b/backend/tests/test_connection_service.py @@ -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. diff --git a/backend/tests/test_core/test_datasets.py b/backend/tests/test_core/test_datasets.py index 6a6696b4..21dd3fcc 100644 --- a/backend/tests/test_core/test_datasets.py +++ b/backend/tests/test_core/test_datasets.py @@ -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 diff --git a/backend/tests/test_core/test_plugin_base.py b/backend/tests/test_core/test_plugin_base.py index f5bad97e..38a43e7a 100644 --- a/backend/tests/test_core/test_plugin_base.py +++ b/backend/tests/test_core/test_plugin_base.py @@ -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 diff --git a/backend/tests/test_core_timezone_edge.py b/backend/tests/test_core_timezone_edge.py index d7ffd499..b1eeed54 100644 --- a/backend/tests/test_core_timezone_edge.py +++ b/backend/tests/test_core_timezone_edge.py @@ -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 diff --git a/backend/tests/test_datasets.py b/backend/tests/test_datasets.py index 7d7d1330..b344888b 100644 --- a/backend/tests/test_datasets.py +++ b/backend/tests/test_datasets.py @@ -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("") == "" diff --git a/backend/tests/test_db_executor.py b/backend/tests/test_db_executor.py index 6b3beb71..c0ec8057 100644 --- a/backend/tests/test_db_executor.py +++ b/backend/tests/test_db_executor.py @@ -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 diff --git a/backend/tests/test_models.py b/backend/tests/test_models.py index 80821440..3090f081 100644 --- a/backend/tests/test_models.py +++ b/backend/tests/test_models.py @@ -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 diff --git a/backend/tests/test_orchestrator_direct_db.py b/backend/tests/test_orchestrator_direct_db.py index d48d2203..90a6b189 100644 --- a/backend/tests/test_orchestrator_direct_db.py +++ b/backend/tests/test_orchestrator_direct_db.py @@ -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. diff --git a/backend/tests/test_schemas_edge.py b/backend/tests/test_schemas_edge.py index 86bf95ca..32315f45 100644 --- a/backend/tests/test_schemas_edge.py +++ b/backend/tests/test_schemas_edge.py @@ -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 diff --git a/backend/tests/test_smoke_app.py b/backend/tests/test_smoke_app.py index a5c37839..4785f104 100644 --- a/backend/tests/test_smoke_app.py +++ b/backend/tests/test_smoke_app.py @@ -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. diff --git a/backend/tests/test_smoke_plugins.py b/backend/tests/test_smoke_plugins.py index ed95b2e1..231c982f 100644 --- a/backend/tests/test_smoke_plugins.py +++ b/backend/tests/test_smoke_plugins.py @@ -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