feat: Introduce and enforce test contract annotations for critical modules and update coverage tracking.
This commit is contained in:
@@ -97,17 +97,17 @@ def test_get_dashboards_with_search(mock_deps):
|
||||
mock_deps["config"].get_environments.return_value = [mock_env]
|
||||
mock_deps["task"].get_all_tasks.return_value = []
|
||||
|
||||
async def mock_get_dashboards(env, tasks):
|
||||
async def mock_get_dashboards(env, tasks, include_git_status=False):
|
||||
return [
|
||||
{"id": 1, "title": "Sales Report", "slug": "sales"},
|
||||
{"id": 2, "title": "Marketing Dashboard", "slug": "marketing"}
|
||||
{"id": 1, "title": "Sales Report", "slug": "sales", "git_status": {"branch": "main", "sync_status": "OK"}, "last_task": None},
|
||||
{"id": 2, "title": "Marketing Dashboard", "slug": "marketing", "git_status": {"branch": "main", "sync_status": "OK"}, "last_task": None}
|
||||
]
|
||||
mock_deps["resource"].get_dashboards_with_status = AsyncMock(
|
||||
side_effect=mock_get_dashboards
|
||||
)
|
||||
|
||||
response = client.get("/api/dashboards?env_id=prod&search=sales")
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
# @POST: Filtered result count must match search
|
||||
|
||||
@@ -4,30 +4,30 @@
|
||||
# @PURPOSE: Defines the FastAPI router for task-related endpoints, allowing clients to create, list, and get the status of tasks.
|
||||
# @LAYER: UI (API)
|
||||
# @RELATION: Depends on the TaskManager. It is included by the main app.
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing import List, Dict, Any, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from pydantic import BaseModel
|
||||
from ...core.logger import belief_scope
|
||||
|
||||
from ...core.task_manager import TaskManager, Task, TaskStatus, LogEntry
|
||||
from ...core.task_manager.models import LogFilter, LogStats
|
||||
from ...dependencies import get_task_manager, has_permission, get_current_user, get_config_manager
|
||||
from ...core.config_manager import ConfigManager
|
||||
from ...services.llm_prompt_templates import (
|
||||
is_multimodal_model,
|
||||
normalize_llm_settings,
|
||||
resolve_bound_provider_id,
|
||||
)
|
||||
from ...core.task_manager import TaskManager, Task, TaskStatus, LogEntry
|
||||
from ...core.task_manager.models import LogFilter, LogStats
|
||||
from ...dependencies import get_task_manager, has_permission, get_current_user, get_config_manager
|
||||
from ...core.config_manager import ConfigManager
|
||||
from ...services.llm_prompt_templates import (
|
||||
is_multimodal_model,
|
||||
normalize_llm_settings,
|
||||
resolve_bound_provider_id,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
TASK_TYPE_PLUGIN_MAP = {
|
||||
"llm_validation": ["llm_dashboard_validation"],
|
||||
"backup": ["superset-backup"],
|
||||
"migration": ["superset-migration"],
|
||||
}
|
||||
|
||||
class CreateTaskRequest(BaseModel):
|
||||
router = APIRouter()
|
||||
|
||||
TASK_TYPE_PLUGIN_MAP = {
|
||||
"llm_validation": ["llm_dashboard_validation"],
|
||||
"backup": ["superset-backup"],
|
||||
"migration": ["superset-migration"],
|
||||
}
|
||||
|
||||
class CreateTaskRequest(BaseModel):
|
||||
plugin_id: str
|
||||
params: Dict[str, Any]
|
||||
|
||||
@@ -45,54 +45,54 @@ class ResumeTaskRequest(BaseModel):
|
||||
# @PRE: plugin_id must exist and params must be valid for that plugin.
|
||||
# @POST: A new task is created and started.
|
||||
# @RETURN: Task - The created task instance.
|
||||
async def create_task(
|
||||
request: CreateTaskRequest,
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
current_user = Depends(get_current_user),
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
async def create_task(
|
||||
request: CreateTaskRequest,
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
current_user = Depends(get_current_user),
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
# Dynamic permission check based on plugin_id
|
||||
has_permission(f"plugin:{request.plugin_id}", "EXECUTE")(current_user)
|
||||
"""
|
||||
Create and start a new task for a given plugin.
|
||||
"""
|
||||
with belief_scope("create_task"):
|
||||
try:
|
||||
# Special handling for LLM tasks to resolve provider config by task binding.
|
||||
if request.plugin_id in {"llm_dashboard_validation", "llm_documentation"}:
|
||||
from ...core.database import SessionLocal
|
||||
from ...services.llm_provider import LLMProviderService
|
||||
db = SessionLocal()
|
||||
try:
|
||||
llm_service = LLMProviderService(db)
|
||||
provider_id = request.params.get("provider_id")
|
||||
if not provider_id:
|
||||
llm_settings = normalize_llm_settings(config_manager.get_config().settings.llm)
|
||||
binding_key = "dashboard_validation" if request.plugin_id == "llm_dashboard_validation" else "documentation"
|
||||
provider_id = resolve_bound_provider_id(llm_settings, binding_key)
|
||||
if provider_id:
|
||||
request.params["provider_id"] = provider_id
|
||||
if not provider_id:
|
||||
providers = llm_service.get_all_providers()
|
||||
active_provider = next((p for p in providers if p.is_active), None)
|
||||
if active_provider:
|
||||
provider_id = active_provider.id
|
||||
request.params["provider_id"] = provider_id
|
||||
|
||||
if provider_id:
|
||||
db_provider = llm_service.get_provider(provider_id)
|
||||
if not db_provider:
|
||||
raise ValueError(f"LLM Provider {provider_id} not found")
|
||||
if request.plugin_id == "llm_dashboard_validation" and not is_multimodal_model(
|
||||
db_provider.default_model,
|
||||
db_provider.provider_type,
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Selected provider model is not multimodal for dashboard validation",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
try:
|
||||
# Special handling for LLM tasks to resolve provider config by task binding.
|
||||
if request.plugin_id in {"llm_dashboard_validation", "llm_documentation"}:
|
||||
from ...core.database import SessionLocal
|
||||
from ...services.llm_provider import LLMProviderService
|
||||
db = SessionLocal()
|
||||
try:
|
||||
llm_service = LLMProviderService(db)
|
||||
provider_id = request.params.get("provider_id")
|
||||
if not provider_id:
|
||||
llm_settings = normalize_llm_settings(config_manager.get_config().settings.llm)
|
||||
binding_key = "dashboard_validation" if request.plugin_id == "llm_dashboard_validation" else "documentation"
|
||||
provider_id = resolve_bound_provider_id(llm_settings, binding_key)
|
||||
if provider_id:
|
||||
request.params["provider_id"] = provider_id
|
||||
if not provider_id:
|
||||
providers = llm_service.get_all_providers()
|
||||
active_provider = next((p for p in providers if p.is_active), None)
|
||||
if active_provider:
|
||||
provider_id = active_provider.id
|
||||
request.params["provider_id"] = provider_id
|
||||
|
||||
if provider_id:
|
||||
db_provider = llm_service.get_provider(provider_id)
|
||||
if not db_provider:
|
||||
raise ValueError(f"LLM Provider {provider_id} not found")
|
||||
if request.plugin_id == "llm_dashboard_validation" and not is_multimodal_model(
|
||||
db_provider.default_model,
|
||||
db_provider.provider_type,
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Selected provider model is not multimodal for dashboard validation",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
task = await task_manager.create_task(
|
||||
plugin_id=request.plugin_id,
|
||||
@@ -113,36 +113,36 @@ async def create_task(
|
||||
# @PRE: task_manager must be available.
|
||||
# @POST: Returns a list of tasks.
|
||||
# @RETURN: List[Task] - List of tasks.
|
||||
async def list_tasks(
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
status_filter: Optional[TaskStatus] = Query(None, alias="status"),
|
||||
task_type: Optional[str] = Query(None, description="Task category: llm_validation, backup, migration"),
|
||||
plugin_id: Optional[List[str]] = Query(None, description="Filter by plugin_id (repeatable query param)"),
|
||||
completed_only: bool = Query(False, description="Return only completed tasks (SUCCESS/FAILED)"),
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
_ = Depends(has_permission("tasks", "READ"))
|
||||
):
|
||||
"""
|
||||
Retrieve a list of tasks with pagination and optional status filter.
|
||||
"""
|
||||
with belief_scope("list_tasks"):
|
||||
plugin_filters = list(plugin_id) if plugin_id else []
|
||||
if task_type:
|
||||
if task_type not in TASK_TYPE_PLUGIN_MAP:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Unsupported task_type '{task_type}'. Allowed: {', '.join(TASK_TYPE_PLUGIN_MAP.keys())}"
|
||||
)
|
||||
plugin_filters.extend(TASK_TYPE_PLUGIN_MAP[task_type])
|
||||
|
||||
return task_manager.get_tasks(
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
status=status_filter,
|
||||
plugin_ids=plugin_filters or None,
|
||||
completed_only=completed_only
|
||||
)
|
||||
async def list_tasks(
|
||||
limit: int = 10,
|
||||
offset: int = 0,
|
||||
status_filter: Optional[TaskStatus] = Query(None, alias="status"),
|
||||
task_type: Optional[str] = Query(None, description="Task category: llm_validation, backup, migration"),
|
||||
plugin_id: Optional[List[str]] = Query(None, description="Filter by plugin_id (repeatable query param)"),
|
||||
completed_only: bool = Query(False, description="Return only completed tasks (SUCCESS/FAILED)"),
|
||||
task_manager: TaskManager = Depends(get_task_manager),
|
||||
_ = Depends(has_permission("tasks", "READ"))
|
||||
):
|
||||
"""
|
||||
Retrieve a list of tasks with pagination and optional status filter.
|
||||
"""
|
||||
with belief_scope("list_tasks"):
|
||||
plugin_filters = list(plugin_id) if plugin_id else []
|
||||
if task_type:
|
||||
if task_type not in TASK_TYPE_PLUGIN_MAP:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Unsupported task_type '{task_type}'. Allowed: {', '.join(TASK_TYPE_PLUGIN_MAP.keys())}"
|
||||
)
|
||||
plugin_filters.extend(TASK_TYPE_PLUGIN_MAP[task_type])
|
||||
|
||||
return task_manager.get_tasks(
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
status=status_filter,
|
||||
plugin_ids=plugin_filters or None,
|
||||
completed_only=completed_only
|
||||
)
|
||||
# [/DEF:list_tasks:Function]
|
||||
|
||||
@router.get("/{task_id}", response_model=Task)
|
||||
@@ -182,6 +182,13 @@ async def get_task(
|
||||
# @POST: Returns a list of log entries or raises 404.
|
||||
# @RETURN: List[LogEntry] - List of log entries.
|
||||
# @TIER: CRITICAL
|
||||
# @TEST_CONTRACT: TaskLogQueryInput -> List[LogEntry]
|
||||
# @TEST_SCENARIO: existing_task_logs_filtered -> Returns filtered logs by level/source/search with pagination.
|
||||
# @TEST_FIXTURE: valid_task_with_mixed_logs -> backend/tests/fixtures/task_logs/valid_task_with_mixed_logs.json
|
||||
# @TEST_EDGE: missing_task -> Unknown task_id returns 404 Task not found.
|
||||
# @TEST_EDGE: invalid_level_type -> Non-string/invalid level query rejected by validation or yields empty result.
|
||||
# @TEST_EDGE: pagination_bounds -> offset=0 and limit=1000 remain within API bounds and do not overflow.
|
||||
# @TEST_INVARIANT: logs_only_for_existing_task -> VERIFIED_BY: [existing_task_logs_filtered, missing_task]
|
||||
async def get_task_logs(
|
||||
task_id: str,
|
||||
level: Optional[str] = Query(None, description="Filter by log level (DEBUG, INFO, WARNING, ERROR)"),
|
||||
@@ -328,4 +335,4 @@ async def clear_tasks(
|
||||
task_manager.clear_tasks(status)
|
||||
return
|
||||
# [/DEF:clear_tasks:Function]
|
||||
# [/DEF:TasksRouter:Module]
|
||||
# [/DEF:TasksRouter:Module]
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
# @LAYER: Domain
|
||||
# @RELATION: BINDS_TO -> specs/023-clean-repo-enterprise/data-model.md
|
||||
# @INVARIANT: Enterprise-clean policy always forbids external sources.
|
||||
# @TEST_CONTRACT: CleanReleaseModelPayload -> ValidatedCleanReleaseModel | ValidationError
|
||||
# @TEST_SCENARIO: valid_enterprise_models -> CRITICAL entities validate and preserve lifecycle/compliance invariants.
|
||||
# @TEST_FIXTURE: clean_release_models_baseline -> backend/tests/fixtures/clean_release/fixtures_clean_release.json
|
||||
# @TEST_EDGE: empty_required_identifiers -> Empty candidate_id/source_snapshot_ref/internal_source_registry_ref fails validation.
|
||||
# @TEST_EDGE: compliant_run_missing_mandatory_stage -> COMPLIANT run without all mandatory PASS stages fails validation.
|
||||
# @TEST_EDGE: blocked_report_without_blocking_violations -> BLOCKED report with zero blocking violations fails validation.
|
||||
# @TEST_INVARIANT: external_source_must_block -> VERIFIED_BY: [valid_enterprise_models, blocked_report_without_blocking_violations]
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"changed_by_name": "Superset Admin",
|
||||
"changed_on": "2026-02-10T13:39:35.945662",
|
||||
"changed_on_delta_humanized": "16 days ago",
|
||||
"changed_on": "2026-02-24T19:24:01.850617",
|
||||
"changed_on_delta_humanized": "7 days ago",
|
||||
"charts": [
|
||||
"TA-0001-001 test_chart"
|
||||
],
|
||||
@@ -19,12 +19,12 @@
|
||||
"id": 1,
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"created_on_delta_humanized": "16 days ago",
|
||||
"created_on_delta_humanized": "13 days ago",
|
||||
"css": null,
|
||||
"dashboard_title": "TA-0001 Test dashboard",
|
||||
"id": 13,
|
||||
"is_managed_externally": false,
|
||||
"json_metadata": "{\"color_scheme_domain\": [], \"shared_label_colors\": [], \"map_label_colors\": {}, \"label_colors\": {}}",
|
||||
"json_metadata": "{\"color_scheme_domain\": [], \"shared_label_colors\": [], \"map_label_colors\": {}, \"label_colors\": {}, \"native_filter_configuration\": []}",
|
||||
"owners": [
|
||||
{
|
||||
"first_name": "Superset",
|
||||
@@ -32,13 +32,13 @@
|
||||
"last_name": "Admin"
|
||||
}
|
||||
],
|
||||
"position_json": null,
|
||||
"position_json": "{\"DASHBOARD_VERSION_KEY\": \"v2\", \"ROOT_ID\": {\"children\": [\"GRID_ID\"], \"id\": \"ROOT_ID\", \"type\": \"ROOT\"}, \"GRID_ID\": {\"children\": [\"ROW-N-LH8TG1XX\"], \"id\": \"GRID_ID\", \"parents\": [\"ROOT_ID\"], \"type\": \"GRID\"}, \"HEADER_ID\": {\"id\": \"HEADER_ID\", \"meta\": {\"text\": \"TA-0001 Test dashboard\"}, \"type\": \"HEADER\"}, \"ROW-N-LH8TG1XX\": {\"children\": [\"CHART-1EKC8H7C\"], \"id\": \"ROW-N-LH8TG1XX\", \"meta\": {\"0\": \"ROOT_ID\", \"background\": \"BACKGROUND_TRANSPARENT\"}, \"type\": \"ROW\", \"parents\": [\"ROOT_ID\", \"GRID_ID\"]}, \"CHART-1EKC8H7C\": {\"children\": [], \"id\": \"CHART-1EKC8H7C\", \"meta\": {\"chartId\": 162, \"height\": 50, \"sliceName\": \"TA-0001-001 test_chart\", \"uuid\": \"008cdaa7-21b3-4042-9f55-f15653609ebd\", \"width\": 4}, \"type\": \"CHART\", \"parents\": [\"ROOT_ID\", \"GRID_ID\", \"ROW-N-LH8TG1XX\"]}}",
|
||||
"published": true,
|
||||
"roles": [],
|
||||
"slug": null,
|
||||
"tags": [],
|
||||
"theme": null,
|
||||
"thumbnail_url": "/api/v1/dashboard/13/thumbnail/3cfc57e6aea7188b139f94fb437a1426/",
|
||||
"thumbnail_url": "/api/v1/dashboard/13/thumbnail/97dfd5d8d24f7cf01de45671c9a0699d/",
|
||||
"url": "/superset/dashboard/13/",
|
||||
"uuid": "124b28d4-d54a-4ade-ade7-2d0473b90686"
|
||||
}
|
||||
@@ -53,15 +53,15 @@
|
||||
"first_name": "Superset",
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"changed_on": "2026-02-10T13:38:26.175551",
|
||||
"changed_on_humanized": "16 days ago",
|
||||
"changed_on": "2026-02-18T14:56:04.863722",
|
||||
"changed_on_humanized": "13 days ago",
|
||||
"column_formats": {},
|
||||
"columns": [
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158196",
|
||||
"column_name": "color",
|
||||
"created_on": "2026-02-10T13:38:26.158189",
|
||||
"changed_on": "2026-02-18T14:56:05.382289",
|
||||
"column_name": "has_2fa",
|
||||
"created_on": "2026-02-18T14:56:05.382138",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -71,16 +71,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "4fa810ee-99cc-4d1f-8c0d-0f289c3b01f4",
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "fe374f2a-9e06-4708-89fd-c3926e3e5faa",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158249",
|
||||
"column_name": "deleted",
|
||||
"created_on": "2026-02-10T13:38:26.158245",
|
||||
"changed_on": "2026-02-18T14:56:05.545701",
|
||||
"column_name": "is_ultra_restricted",
|
||||
"created_on": "2026-02-18T14:56:05.545465",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -92,14 +92,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "ebc07e82-7250-4eef-8d13-ea61561fa52c",
|
||||
"uuid": "eac7ecce-d472-4933-9652-d4f2811074fd",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158289",
|
||||
"column_name": "has_2fa",
|
||||
"created_on": "2026-02-10T13:38:26.158285",
|
||||
"changed_on": "2026-02-18T14:56:05.683578",
|
||||
"column_name": "is_primary_owner",
|
||||
"created_on": "2026-02-18T14:56:05.683257",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -111,14 +111,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "08e72f4d-3ced-4d9a-9f7d-2f85291ce88b",
|
||||
"uuid": "94a15acd-ef98-425b-8f0d-1ce038ca95c5",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158328",
|
||||
"column_name": "id",
|
||||
"created_on": "2026-02-10T13:38:26.158324",
|
||||
"changed_on": "2026-02-18T14:56:05.758231",
|
||||
"column_name": "is_app_user",
|
||||
"created_on": "2026-02-18T14:56:05.758142",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -128,16 +128,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "fd11955c-0130-4ea1-b3c0-d8b159971789",
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "d3fcd712-dc96-4bba-a026-aa82022eccf5",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158366",
|
||||
"changed_on": "2026-02-18T14:56:05.799597",
|
||||
"column_name": "is_admin",
|
||||
"created_on": "2026-02-10T13:38:26.158362",
|
||||
"created_on": "2026-02-18T14:56:05.799519",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -149,14 +149,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "13a6c8e1-c9f8-4f08-aa62-05bca7be547b",
|
||||
"uuid": "5a1c9de5-80f1-4fe8-a91b-e6e530688aae",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158404",
|
||||
"column_name": "is_app_user",
|
||||
"created_on": "2026-02-10T13:38:26.158400",
|
||||
"changed_on": "2026-02-18T14:56:05.819443",
|
||||
"column_name": "is_bot",
|
||||
"created_on": "2026-02-18T14:56:05.819382",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -168,14 +168,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "6321ba8a-28d7-4d68-a6b3-5cef6cd681a2",
|
||||
"uuid": "6c93e5de-e0d7-430c-88d7-87158905d60a",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158442",
|
||||
"column_name": "is_bot",
|
||||
"created_on": "2026-02-10T13:38:26.158438",
|
||||
"changed_on": "2026-02-18T14:56:05.827568",
|
||||
"column_name": "is_restricted",
|
||||
"created_on": "2026-02-18T14:56:05.827556",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -187,14 +187,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "f3ded50e-b1a2-4a88-b805-781d5923e062",
|
||||
"uuid": "2e8e6d32-0124-4e3a-a53f-6f200f852439",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158480",
|
||||
"changed_on": "2026-02-18T14:56:05.835380",
|
||||
"column_name": "is_owner",
|
||||
"created_on": "2026-02-10T13:38:26.158477",
|
||||
"created_on": "2026-02-18T14:56:05.835366",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -206,14 +206,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "8a1408eb-050d-4455-878c-22342df5da3d",
|
||||
"uuid": "510d651b-a595-4261-98e4-278af0a06594",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158532",
|
||||
"column_name": "is_primary_owner",
|
||||
"created_on": "2026-02-10T13:38:26.158528",
|
||||
"changed_on": "2026-02-18T14:56:05.843802",
|
||||
"column_name": "deleted",
|
||||
"created_on": "2026-02-18T14:56:05.843784",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -225,14 +225,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "054b8c16-82fd-480c-82e0-a0975229673a",
|
||||
"uuid": "2653fd2f-c0ce-484e-a5df-d2515b1e822d",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158583",
|
||||
"column_name": "is_restricted",
|
||||
"created_on": "2026-02-10T13:38:26.158579",
|
||||
"changed_on": "2026-02-18T14:56:05.851074",
|
||||
"column_name": "updated",
|
||||
"created_on": "2026-02-18T14:56:05.851063",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -240,18 +240,18 @@
|
||||
"groupby": true,
|
||||
"id": 781,
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"is_dttm": true,
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "6932c25f-0273-4595-85c1-29422a801ded",
|
||||
"type": "DATETIME",
|
||||
"type_generic": 2,
|
||||
"uuid": "1b1f90c8-2567-49b8-9398-e7246396461e",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158621",
|
||||
"column_name": "is_ultra_restricted",
|
||||
"created_on": "2026-02-10T13:38:26.158618",
|
||||
"changed_on": "2026-02-18T14:56:05.857578",
|
||||
"column_name": "tz_offset",
|
||||
"created_on": "2026-02-18T14:56:05.857571",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -261,16 +261,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "BOOLEAN",
|
||||
"type_generic": 3,
|
||||
"uuid": "9b14e5f9-3ab4-498e-b1e3-bbf49e9d61fe",
|
||||
"type": "LONGINTEGER",
|
||||
"type_generic": 0,
|
||||
"uuid": "e6d19b74-7f5d-447b-8071-951961dc2295",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158660",
|
||||
"column_name": "name",
|
||||
"created_on": "2026-02-10T13:38:26.158656",
|
||||
"changed_on": "2026-02-18T14:56:05.863101",
|
||||
"column_name": "channel_name",
|
||||
"created_on": "2026-02-18T14:56:05.863094",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -282,14 +282,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "ebee8249-0e10-4157-8a8e-96ae107887a3",
|
||||
"uuid": "e1f34628-ebc1-4e0c-8eea-54c3c9efba1b",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158697",
|
||||
"changed_on": "2026-02-18T14:56:05.877136",
|
||||
"column_name": "real_name",
|
||||
"created_on": "2026-02-10T13:38:26.158694",
|
||||
"created_on": "2026-02-18T14:56:05.877083",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -301,14 +301,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "553517a0-fe05-4ff5-a4eb-e9d2165d6f64",
|
||||
"uuid": "6cc5ab57-9431-428a-a331-0a5b10e4b074",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158735",
|
||||
"column_name": "team_id",
|
||||
"created_on": "2026-02-10T13:38:26.158731",
|
||||
"changed_on": "2026-02-18T14:56:05.893859",
|
||||
"column_name": "tz_label",
|
||||
"created_on": "2026-02-18T14:56:05.893834",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -320,14 +320,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "6c207fac-424d-465c-b80a-306b42b55ce8",
|
||||
"uuid": "8e6dbd8e-b880-4517-a5f6-64e429bd1bea",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158773",
|
||||
"column_name": "tz",
|
||||
"created_on": "2026-02-10T13:38:26.158769",
|
||||
"changed_on": "2026-02-18T14:56:05.902363",
|
||||
"column_name": "team_id",
|
||||
"created_on": "2026-02-18T14:56:05.902352",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -339,14 +339,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "6efcc042-0b78-4362-9373-2f684077d574",
|
||||
"uuid": "ba8e225d-221b-4275-aadb-e79557756f89",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158824",
|
||||
"column_name": "tz_label",
|
||||
"created_on": "2026-02-10T13:38:26.158820",
|
||||
"changed_on": "2026-02-18T14:56:05.910169",
|
||||
"column_name": "name",
|
||||
"created_on": "2026-02-18T14:56:05.910151",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -358,14 +358,14 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "c6a6ac40-5c60-472d-a878-4b65b8460ccc",
|
||||
"uuid": "02a7a026-d9f3-49e9-9586-534ebccdd867",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158861",
|
||||
"column_name": "tz_offset",
|
||||
"created_on": "2026-02-10T13:38:26.158857",
|
||||
"changed_on": "2026-02-18T14:56:05.915366",
|
||||
"column_name": "color",
|
||||
"created_on": "2026-02-18T14:56:05.915357",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -375,16 +375,16 @@
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "LONGINTEGER",
|
||||
"type_generic": 0,
|
||||
"uuid": "cf6da93a-bba9-47df-9154-6cfd0c9922fc",
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "0702fcdf-2d03-45db-8496-697d47b300d6",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158913",
|
||||
"column_name": "updated",
|
||||
"created_on": "2026-02-10T13:38:26.158909",
|
||||
"changed_on": "2026-02-18T14:56:05.919466",
|
||||
"column_name": "id",
|
||||
"created_on": "2026-02-18T14:56:05.919460",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -392,18 +392,18 @@
|
||||
"groupby": true,
|
||||
"id": 789,
|
||||
"is_active": true,
|
||||
"is_dttm": true,
|
||||
"is_dttm": false,
|
||||
"python_date_format": null,
|
||||
"type": "DATETIME",
|
||||
"type_generic": 2,
|
||||
"uuid": "2aa0a72a-5602-4799-b5ab-f22000108d62",
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "a4b58528-fcbf-45e9-af39-fe9d737ba380",
|
||||
"verbose_name": null
|
||||
},
|
||||
{
|
||||
"advanced_data_type": null,
|
||||
"changed_on": "2026-02-10T13:38:26.158967",
|
||||
"column_name": "channel_name",
|
||||
"created_on": "2026-02-10T13:38:26.158963",
|
||||
"changed_on": "2026-02-18T14:56:05.932553",
|
||||
"column_name": "tz",
|
||||
"created_on": "2026-02-18T14:56:05.932530",
|
||||
"description": null,
|
||||
"expression": null,
|
||||
"extra": null,
|
||||
@@ -415,7 +415,7 @@
|
||||
"python_date_format": null,
|
||||
"type": "STRING",
|
||||
"type_generic": 1,
|
||||
"uuid": "a84bd658-c83c-4e7f-9e1b-192595092d9b",
|
||||
"uuid": "bc872357-1920-42f3-aeda-b596122bcdb8",
|
||||
"verbose_name": null
|
||||
}
|
||||
],
|
||||
@@ -423,8 +423,8 @@
|
||||
"first_name": "Superset",
|
||||
"last_name": "Admin"
|
||||
},
|
||||
"created_on": "2026-02-10T13:38:26.050436",
|
||||
"created_on_humanized": "16 days ago",
|
||||
"created_on": "2026-02-18T14:56:04.317950",
|
||||
"created_on_humanized": "13 days ago",
|
||||
"database": {
|
||||
"allow_multi_catalog": false,
|
||||
"backend": "postgresql",
|
||||
@@ -452,8 +452,8 @@
|
||||
"main_dttm_col": "updated",
|
||||
"metrics": [
|
||||
{
|
||||
"changed_on": "2026-02-10T13:38:26.182269",
|
||||
"created_on": "2026-02-10T13:38:26.182264",
|
||||
"changed_on": "2026-02-18T14:56:05.085244",
|
||||
"created_on": "2026-02-18T14:56:05.085166",
|
||||
"currency": null,
|
||||
"d3format": null,
|
||||
"description": null,
|
||||
@@ -462,7 +462,7 @@
|
||||
"id": 33,
|
||||
"metric_name": "count",
|
||||
"metric_type": "count",
|
||||
"uuid": "7510f8ca-05ee-4a37-bec1-4a5d7bf2ac50",
|
||||
"uuid": "10c8b8cf-b697-4512-9e9e-2996721f829e",
|
||||
"verbose_name": "COUNT(*)",
|
||||
"warning_text": null
|
||||
}
|
||||
|
||||
@@ -48,6 +48,33 @@ def test_orchestrator_stage_failure_blocks_release():
|
||||
# [/DEF:test_orchestrator_stage_failure_blocks_release:Function]
|
||||
|
||||
|
||||
# [DEF:test_orchestrator_compliant_candidate:Function]
|
||||
# @PURPOSE: Verify happy path where all mandatory stages pass yields COMPLIANT.
|
||||
def test_orchestrator_compliant_candidate():
|
||||
repository = CleanReleaseRepository()
|
||||
orchestrator = CleanComplianceOrchestrator(repository)
|
||||
|
||||
run = orchestrator.start_check_run(
|
||||
candidate_id="2026.03.03-rc1",
|
||||
policy_id="policy-enterprise-clean-v1",
|
||||
triggered_by="tester",
|
||||
execution_mode="tui",
|
||||
)
|
||||
run = orchestrator.execute_stages(
|
||||
run,
|
||||
forced_results=[
|
||||
CheckStageResult(stage=CheckStageName.DATA_PURITY, status=CheckStageStatus.PASS, details="ok"),
|
||||
CheckStageResult(stage=CheckStageName.INTERNAL_SOURCES_ONLY, status=CheckStageStatus.PASS, details="ok"),
|
||||
CheckStageResult(stage=CheckStageName.NO_EXTERNAL_ENDPOINTS, status=CheckStageStatus.PASS, details="ok"),
|
||||
CheckStageResult(stage=CheckStageName.MANIFEST_CONSISTENCY, status=CheckStageStatus.PASS, details="ok"),
|
||||
],
|
||||
)
|
||||
run = orchestrator.finalize_run(run)
|
||||
|
||||
assert run.final_status == CheckFinalStatus.COMPLIANT
|
||||
# [/DEF:test_orchestrator_compliant_candidate:Function]
|
||||
|
||||
|
||||
# [DEF:test_orchestrator_missing_stage_result:Function]
|
||||
# @PURPOSE: Verify incomplete mandatory stage set cannot end as COMPLIANT and results in FAILED.
|
||||
def test_orchestrator_missing_stage_result():
|
||||
|
||||
@@ -66,6 +66,26 @@ def test_report_builder_blocked_requires_blocking_violations():
|
||||
# [/DEF:test_report_builder_blocked_requires_blocking_violations:Function]
|
||||
|
||||
|
||||
# [DEF:test_report_builder_blocked_with_two_violations:Function]
|
||||
# @PURPOSE: Verify report builder generates conformant payload for a BLOCKED run with violations.
|
||||
def test_report_builder_blocked_with_two_violations():
|
||||
builder = ComplianceReportBuilder(CleanReleaseRepository())
|
||||
run = _terminal_run(CheckFinalStatus.BLOCKED)
|
||||
v1 = _blocking_violation()
|
||||
v2 = _blocking_violation()
|
||||
v2.violation_id = "viol-2"
|
||||
v2.category = ViolationCategory.DATA_PURITY
|
||||
|
||||
report = builder.build_report_payload(run, [v1, v2])
|
||||
|
||||
assert report.check_run_id == run.check_run_id
|
||||
assert report.candidate_id == run.candidate_id
|
||||
assert report.final_status == CheckFinalStatus.BLOCKED
|
||||
assert report.violations_count == 2
|
||||
assert report.blocking_violations_count == 2
|
||||
# [/DEF:test_report_builder_blocked_with_two_violations:Function]
|
||||
|
||||
|
||||
# [DEF:test_report_builder_counter_consistency:Function]
|
||||
# @PURPOSE: Verify violations counters remain consistent for blocking payload.
|
||||
def test_report_builder_counter_consistency():
|
||||
|
||||
Reference in New Issue
Block a user