# #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