test: add 7 more test modules — assistant cmd parser, history, dispatch, admin routes, dataset review, maintenance + semantic resolver

This commit is contained in:
2026-06-15 16:06:18 +03:00
parent f75c15dbc6
commit fc7e6c3c15
9 changed files with 3241 additions and 0 deletions

View File

@@ -0,0 +1,367 @@
# #region Test.Assistant.CommandParser [C:2] [TYPE Module] [SEMANTICS test,assistant,command,parser,nlu]
# @BRIEF Tests for _command_parser.py — deterministic RU/EN command parser.
# @RELATION BINDS_TO -> [AssistantCommandParser]
# @TEST_EDGE: help_variants -> All help phrases map to show_capabilities
# @TEST_EDGE: status_variants -> Status commands get_task_status with/without task_id
# @TEST_EDGE: branch_create -> Branch create with dashboard_id
# @TEST_EDGE: commit_variants -> Commit with quoted message
# @TEST_EDGE: deploy_variants -> Deploy with env, production detection
# @TEST_EDGE: migrate_variants -> Migration with src/tgt/dry_run/replace_db
# @TEST_EDGE: backup_variants -> Backup with env
# @TEST_EDGE: health_variants -> Health check with env
# @TEST_EDGE: validation_variants -> LLM validation with provider
# @TEST_EDGE: documentation_variants -> LLM documentation with dataset_id
# @TEST_EDGE: fallback -> Unknown commands return clarify intent
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from unittest.mock import MagicMock
import pytest
def _make_config_manager(envs=None):
cm = MagicMock()
envs = envs or []
cm.get_environments = MagicMock(return_value=envs)
return cm
def _make_env(id_, name_, stage="DEV", is_production=False, is_default=False):
env = MagicMock()
env.id = id_
env.name = name_
env.stage = stage
env.is_production = is_production
env.is_default = is_default
return env
class TestParseCommandHelp:
"""_parse_command — help/capabilities variants."""
@pytest.mark.parametrize("msg", [
"что ты умеешь",
"что умеешь",
"что ты можешь",
"help",
"помощь",
"доступные команды",
"какие команды",
])
def test_help_variants(self, msg):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "assistant"
assert result["operation"] == "show_capabilities"
assert result["entities"] == {}
assert result["confidence"] == 0.98
assert result["risk_level"] == "safe"
assert result["requires_confirmation"] is False
class TestParseCommandStatus:
"""_parse_command — status/task status variants."""
@pytest.mark.parametrize("msg,expected_conf", [
("статус", 0.66),
("status", 0.66),
("state", 0.66),
("проверь задачу", 0.66),
("статус task-abc123", 0.92),
("status task-abc-def", 0.92),
])
def test_status_variants(self, msg, expected_conf):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "status"
assert result["operation"] == "get_task_status"
assert result["confidence"] == expected_conf
assert result["risk_level"] == "safe"
class TestParseCommandBranch:
"""_parse_command — branch create variants."""
@pytest.mark.parametrize("msg,expected_branch,expected_conf", [
("создай ветку feature/test", "feature/test", 0.7),
("create branch feature/abc", "feature/abc", 0.7),
("сделай ветку fix/123 для дашборд 42", "fix/123", 0.95),
("create branch main", "main", 0.7),
])
def test_branch_create(self, msg, expected_branch, expected_conf):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "git"
assert result["operation"] == "create_branch"
assert result["risk_level"] == "guarded"
assert result["confidence"] == expected_conf
class TestParseCommandCommit:
"""_parse_command — commit variants."""
def test_commit_with_message(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command('commit "update dashboard filters"', cm)
assert result["operation"] == "commit_changes"
assert result["entities"]["message"] == "update dashboard filters"
def test_commit_without_quotes(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("commit", cm)
assert result["operation"] == "commit_changes"
assert result["entities"]["message"] == "assistant: update dashboard changes"
class TestParseCommandDeploy:
"""_parse_command — deploy variants with production detection."""
def test_deploy_to_dev(self):
from src.api.routes.assistant._command_parser import _parse_command
envs = [_make_env("dev", "DEV", stage="DEV")]
cm = _make_config_manager(envs)
result = _parse_command("деплой дашборд 42 в dev", cm)
assert result["operation"] == "deploy_dashboard"
assert result["risk_level"] == "guarded"
assert result["requires_confirmation"] is False
def test_deploy_to_production(self):
from src.api.routes.assistant._command_parser import _parse_command
prod = _make_env("prod", "PROD", stage="PROD", is_production=True)
cm = _make_config_manager([prod])
result = _parse_command("deploy dashboard 42 в prod", cm)
assert result["operation"] == "deploy_dashboard"
assert result["risk_level"] == "dangerous"
assert result["requires_confirmation"] is True
def test_deploy_without_env(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("разверн дашборд 42", cm)
assert result["operation"] == "deploy_dashboard"
assert result["confidence"] == 0.7
class TestParseCommandMigration:
"""_parse_command — migration variants."""
def test_migration_with_src_tgt(self):
from src.api.routes.assistant._command_parser import _parse_command
envs = [_make_env("prod", "PROD", stage="PROD", is_production=True)]
cm = _make_config_manager(envs)
result = _parse_command("миграц дашборд 42 с dev на prod", cm)
assert result["operation"] == "execute_migration"
assert result["entities"]["source_env"] == "dev"
assert result["entities"]["target_env"] == "prod"
assert result["risk_level"] == "dangerous" # because tgt=prod
def test_migration_dry_run(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("migrate dashboard 42 from dev to staging --dry-run", cm)
assert result["entities"]["dry_run"] is True
assert result["entities"]["replace_db_config"] is False
assert result["entities"]["fix_cross_filters"] is True
def test_migration_replace_db(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("миграция дашборд 42 с dev на qa --replace-db-config --no-fix-cross-filters", cm)
assert result["entities"]["replace_db_config"] is True
assert result["entities"]["fix_cross_filters"] is False
def test_migration_without_dashboard(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("migrate from dev to staging", cm)
assert result["confidence"] == 0.72
class TestParseCommandBackup:
"""_parse_command — backup variants."""
@pytest.mark.parametrize("msg,expected_env", [
("бэкап в dev", "dev"),
("backup for prod", "prod"),
("резерв из staging", "staging"),
("бэкап", None),
])
def test_backup(self, msg, expected_env):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["operation"] == "run_backup"
assert result["entities"].get("environment") == expected_env
assert result["risk_level"] == "guarded"
class TestParseCommandHealth:
"""_parse_command — health check variants."""
@pytest.mark.parametrize("msg,expected_env", [
("здоровье в dev", "dev"),
("health for prod", "prod"),
("ошибки", None),
("failing env staging", "staging"),
("проблемы", None),
])
def test_health(self, msg, expected_env):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "health"
assert result["operation"] == "get_health_summary"
assert result["entities"].get("environment") == expected_env
assert result["risk_level"] == "safe"
class TestParseCommandValidation:
"""_parse_command — LLM validation variants."""
def test_validation_with_provider(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("валидац дашборд 42 для env dev провайдер openai", cm)
assert result["operation"] == "run_llm_validation"
assert result["entities"]["environment"] == "dev"
assert result["entities"]["provider"] == "openai"
def test_validation_without_env(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
# "проверь" alone without a dashboard id -> low confidence
result = _parse_command("проверь", cm)
assert result["operation"] == "run_llm_validation"
assert result["confidence"] == 0.64
class TestParseCommandDocumentation:
"""_parse_command — LLM documentation variants."""
def test_documentation_with_dataset(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("документац датасет 123 env prod провайдер anthropic", cm)
assert result["operation"] == "run_llm_documentation"
assert result["entities"]["dataset_id"] == 123
assert result["entities"]["environment"] == "prod"
assert result["entities"]["provider"] == "anthropic"
def test_documentation_without_dataset(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("generate docs", cm)
assert result["operation"] == "run_llm_documentation"
assert result["confidence"] == 0.64
class TestParseCommandSearchDashboards:
"""_parse_command — search/list dashboards."""
@pytest.mark.parametrize("msg,expected_env", [
("список дашборд в dev", "dev"),
("покажи дашборд в prod", "prod"),
("найди дашборд test в staging", "staging"),
("list dashboard в dev", "dev"),
("search dashboard covid в dev", "dev"),
("all dashboards", None),
("все дашборд", None),
])
def test_search_dashboards(self, msg, expected_env):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "dashboards"
assert result["operation"] == "search_dashboards"
assert result["entities"].get("environment") == expected_env
class TestParseCommandEnvironments:
"""_parse_command — list environments."""
@pytest.mark.parametrize("msg", [
"окружени",
"environment",
"среды",
"сред",
])
def test_list_environments(self, msg):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "environments"
assert result["operation"] == "list_environments"
assert result["risk_level"] == "safe"
class TestParseCommandMaintenance:
"""_parse_command — maintenance/events."""
@pytest.mark.parametrize("msg", [
"обслуживан",
"maintenance",
"события",
])
def test_list_maintenance(self, msg):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "maintenance"
assert result["operation"] == "list_maintenance_events"
class TestParseCommandLlmProviders:
"""_parse_command — LLM provider listing/status."""
def test_list_providers(self):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("llm провайдер", cm)
assert result["domain"] == "llm"
assert result["operation"] == "list_llm_providers"
def test_llm_status_unreachable_via_parser(self):
"""The deterministic parser cannot deliver get_llm_status because all
keywords matching line 99 are intercepted by earlier checks (status,
validation). This is a known source-code quirk; LLM status routing
happens through the LLM planner path instead."""
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command("llm провер", cm)
# Line 74 (провер in validation) catches it first
assert result["operation"] == "run_llm_validation"
class TestParseCommandFallback:
"""_parse_command — unknown commands return clarify intent."""
@pytest.mark.parametrize("msg", [
"",
"hello world",
"какой сегодня день",
"random gibberish xyz",
"12345",
])
def test_fallback_to_clarify(self, msg):
from src.api.routes.assistant._command_parser import _parse_command
cm = _make_config_manager()
result = _parse_command(msg, cm)
assert result["domain"] == "unknown"
assert result["operation"] == "clarify"
assert result["confidence"] == 0.3
assert result["risk_level"] == "safe"
assert "domain" in result
assert "operation" in result
assert "entities" in result
assert "confidence" in result
assert "risk_level" in result
assert "requires_confirmation" in result
# #endregion Test.Assistant.CommandParser