codebuff-agent 2.1.74__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codebuff_agent-2.1.74.data/data/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/package.json +157 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/150.39fa98117cc8ca590465.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/166.386ff9b2c37ac865fdee.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/216.25cf81131bf7274dabf4.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/353.581bd1cb00746399b62e.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/367.575f9621b63c9a641aa9.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/421.11dac223a2c2da1b09f5.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/481.cc2dec9fa869d66f1407.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/487.0796e931d6e651482c14.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/49.d326f554b6f9ccde6580.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/541.26897ea32845659e36be.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/579.e569d3edd8391fd8fbb3.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/666.d9bfdb23f98489d0a188.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/68.26b6f59c8454adb17f20.js +204 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/694.4ac443814260057e68f7.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/791.61affb8d3480d3d60e0d.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js +2 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/style.js +4 -0
- codebuff_agent-2.1.74.data/data/share/jupyter/labextensions/codebuff/static/third-party-licenses.json +724 -0
- codebuff_agent-2.1.74.dist-info/METADATA +142 -0
- codebuff_agent-2.1.74.dist-info/RECORD +217 -0
- codebuff_agent-2.1.74.dist-info/WHEEL +4 -0
- codebuff_agent-2.1.74.dist-info/licenses/LICENSE +21 -0
- codebuff_core/__init__.py +153 -0
- codebuff_core/codebuff_config_defaults.json +64 -0
- codebuff_core/core/__init__.py +35 -0
- codebuff_core/core/code_validator.py +1238 -0
- codebuff_core/core/error_classifier.py +579 -0
- codebuff_core/core/exceptions.py +49 -0
- codebuff_core/data/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +100256 -0
- codebuff_core/directory_policy.py +32 -0
- codebuff_core/factory.py +155 -0
- codebuff_core/interfaces.py +197 -0
- codebuff_core/knowledge/__init__.py +31 -0
- codebuff_core/knowledge/chunking.py +375 -0
- codebuff_core/knowledge/loader.py +339 -0
- codebuff_core/knowledge/watchdog_service.py +354 -0
- codebuff_core/legacy_paths.py +69 -0
- codebuff_core/llm/__init__.py +13 -0
- codebuff_core/llm/service.py +407 -0
- codebuff_core/managers/__init__.py +22 -0
- codebuff_core/managers/config_manager.py +690 -0
- codebuff_core/managers/remote_config.py +673 -0
- codebuff_core/managers/remote_skills.py +539 -0
- codebuff_core/managers/session_manager.py +255 -0
- codebuff_core/middleware/__init__.py +3 -0
- codebuff_core/middleware/error_recovery.py +394 -0
- codebuff_core/middleware/logging_utils.py +33 -0
- codebuff_core/models/__init__.py +111 -0
- codebuff_core/models/agent.py +306 -0
- codebuff_core/models/chat.py +57 -0
- codebuff_core/models/common.py +82 -0
- codebuff_core/models/rag.py +300 -0
- codebuff_core/platform_rules.py +127 -0
- codebuff_core/prompts/__init__.py +44 -0
- codebuff_core/prompts/auto_agent_prompts.py +1278 -0
- codebuff_core/services/__init__.py +30 -0
- codebuff_core/services/agent_service.py +345 -0
- codebuff_core/services/analytics_aggregate.py +837 -0
- codebuff_core/services/chat_service.py +898 -0
- codebuff_core/services/context_condenser.py +306 -0
- codebuff_core/services/pii_guardrail.py +393 -0
- codebuff_core/services/rag_chat_agent.py +2993 -0
- codebuff_core/services/rag_collections.py +2740 -0
- codebuff_core/services/rag_manager.py +890 -0
- codebuff_core/services/rag_service.py +145 -0
- codebuff_core/services/retriever.py +260 -0
- codebuff_core/services/vllm_embedding_service.py +290 -0
- codebuff_core/tests/__init__.py +1 -0
- codebuff_core/tests/conftest.py +81 -0
- codebuff_core/tests/test_error_recovery.py +647 -0
- codebuff_core/tests/test_factory.py +135 -0
- codebuff_core/tests/test_services.py +150 -0
- jupyter_ext/__init__.py +266 -0
- jupyter_ext/_version.py +4 -0
- jupyter_ext/agent/__init__.py +49 -0
- jupyter_ext/agent/agent_factory.py +2270 -0
- jupyter_ext/agent/ast_tools.py +451 -0
- jupyter_ext/agent/autofix_middleware.py +481 -0
- jupyter_ext/agent/background_procs.py +372 -0
- jupyter_ext/agent/code_history_middleware.py +211 -0
- jupyter_ext/agent/codex_autofix.py +160 -0
- jupyter_ext/agent/codex_bridge.py +3339 -0
- jupyter_ext/agent/codex_hooks.py +320 -0
- jupyter_ext/agent/codex_slash.py +685 -0
- jupyter_ext/agent/codex_threads.py +114 -0
- jupyter_ext/agent/content_injection.py +377 -0
- jupyter_ext/agent/continuation_middleware.py +548 -0
- jupyter_ext/agent/custom_skills.py +335 -0
- jupyter_ext/agent/file_diffs.py +99 -0
- jupyter_ext/agent/git_history.py +188 -0
- jupyter_ext/agent/guardrail_middleware.py +421 -0
- jupyter_ext/agent/json_repair_utils.py +54 -0
- jupyter_ext/agent/langchain_tools.py +2673 -0
- jupyter_ext/agent/mermaid_fixer.py +289 -0
- jupyter_ext/agent/mermaid_middleware.py +135 -0
- jupyter_ext/agent/middleware.py +359 -0
- jupyter_ext/agent/middleware_prompts.py +53 -0
- jupyter_ext/agent/observability.py +334 -0
- jupyter_ext/agent/patch_tool_calls.py +57 -0
- jupyter_ext/agent/prompts.py +112 -0
- jupyter_ext/agent/skill_middleware.py +257 -0
- jupyter_ext/agent/skills/airflow_dag_development.md +36 -0
- jupyter_ext/agent/skills/anomaly_detection.md +12 -0
- jupyter_ext/agent/skills/athena_sql.md +156 -0
- jupyter_ext/agent/skills/bdp_table_search.md +33 -0
- jupyter_ext/agent/skills/bigquery.md +65 -0
- jupyter_ext/agent/skills/bigquery_oracle.md +231 -0
- jupyter_ext/agent/skills/bigquery_tuning.md +307 -0
- jupyter_ext/agent/skills/classification_evaluation.md +11 -0
- jupyter_ext/agent/skills/clustering_analysis.md +12 -0
- jupyter_ext/agent/skills/comprehensive_eda.md +11 -0
- jupyter_ext/agent/skills/data_analysis.md +236 -0
- jupyter_ext/agent/skills/data_cleaning_pipeline.md +12 -0
- jupyter_ext/agent/skills/data_loading.md +158 -0
- jupyter_ext/agent/skills/data_quality_report.md +13 -0
- jupyter_ext/agent/skills/dimensionality_reduction.md +12 -0
- jupyter_ext/agent/skills/emr_script_development.md +17 -0
- jupyter_ext/agent/skills/experiment_tracking.md +13 -0
- jupyter_ext/agent/skills/feature_importance_analysis.md +10 -0
- jupyter_ext/agent/skills/hyperparameter_tuning.md +12 -0
- jupyter_ext/agent/skills/hypothesis_testing.md +12 -0
- jupyter_ext/agent/skills/imbalanced_data_handling.md +12 -0
- jupyter_ext/agent/skills/inference.md +392 -0
- jupyter_ext/agent/skills/lambda_function_development.md +19 -0
- jupyter_ext/agent/skills/model_comparison.md +11 -0
- jupyter_ext/agent/skills/model_explainability.md +12 -0
- jupyter_ext/agent/skills/model_training.md +318 -0
- jupyter_ext/agent/skills/pyspark.md +352 -0
- jupyter_ext/agent/skills/python_code_quality.md +35 -0
- jupyter_ext/agent/skills/regression_evaluation.md +12 -0
- jupyter_ext/agent/skills/spring_java_development.md +35 -0
- jupyter_ext/agent/skills/text_preprocessing.md +12 -0
- jupyter_ext/agent/skills/time_series_analysis.md +13 -0
- jupyter_ext/agent/skills/unit_testing.md +203 -0
- jupyter_ext/agent/todo_prompts.py +57 -0
- jupyter_ext/agent/tool_report.py +45 -0
- jupyter_ext/analytics_ingest.py +599 -0
- jupyter_ext/etc/jupyter/jupyter_server_config.d/codebuff_agent.json +7 -0
- jupyter_ext/handlers/__init__.py +8 -0
- jupyter_ext/handlers/analytics_handler.py +170 -0
- jupyter_ext/handlers/benchmark_handler.py +794 -0
- jupyter_ext/handlers/eda_benchmark_handler.py +1328 -0
- jupyter_ext/handlers/rag_admin_handler.py +689 -0
- jupyter_ext/handlers/websocket.py +497 -0
- jupyter_ext/http_handlers.py +8744 -0
- jupyter_ext/idle_monitor.py +346 -0
- jupyter_ext/labextension/package.json +157 -0
- jupyter_ext/labextension/static/104.2b8f20f9d1ac2c9db01b.js +1 -0
- jupyter_ext/labextension/static/150.39fa98117cc8ca590465.js +1 -0
- jupyter_ext/labextension/static/166.386ff9b2c37ac865fdee.js +1 -0
- jupyter_ext/labextension/static/216.25cf81131bf7274dabf4.js +1 -0
- jupyter_ext/labextension/static/353.581bd1cb00746399b62e.js +1 -0
- jupyter_ext/labextension/static/367.575f9621b63c9a641aa9.js +1 -0
- jupyter_ext/labextension/static/421.11dac223a2c2da1b09f5.js +1 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js +2 -0
- jupyter_ext/labextension/static/432.8513b4c6cc1d1232d165.js.LICENSE.txt +19 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js +2 -0
- jupyter_ext/labextension/static/439.ba5b5ef70ec6de214b8c.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/481.cc2dec9fa869d66f1407.js +1 -0
- jupyter_ext/labextension/static/487.0796e931d6e651482c14.js +1 -0
- jupyter_ext/labextension/static/49.d326f554b6f9ccde6580.js +1 -0
- jupyter_ext/labextension/static/541.26897ea32845659e36be.js +1 -0
- jupyter_ext/labextension/static/579.e569d3edd8391fd8fbb3.js +1 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js +2 -0
- jupyter_ext/labextension/static/58.eb7cc679f7535cc8a2f1.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/666.d9bfdb23f98489d0a188.js +1 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js +2 -0
- jupyter_ext/labextension/static/677.d266c39dbed33bc388b3.js.LICENSE.txt +8 -0
- jupyter_ext/labextension/static/68.26b6f59c8454adb17f20.js +204 -0
- jupyter_ext/labextension/static/694.4ac443814260057e68f7.js +1 -0
- jupyter_ext/labextension/static/791.61affb8d3480d3d60e0d.js +1 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js +2 -0
- jupyter_ext/labextension/static/848.4d2db09cfbb09374b299.js.LICENSE.txt +9 -0
- jupyter_ext/labextension/static/868.5cfe7e1a348ec5af1b8c.js +1 -0
- jupyter_ext/labextension/static/97.ea559a5b8a9a09f17c6b.js +1 -0
- jupyter_ext/labextension/static/remoteEntry.5292fc9c6df7e7920cc8.js +1 -0
- jupyter_ext/labextension/static/style.js +4 -0
- jupyter_ext/labextension/static/third-party-licenses.json +724 -0
- jupyter_ext/log_paths.py +113 -0
- jupyter_ext/resource_usage.py +519 -0
- jupyter_ext/secrets_bootstrap.py +116 -0
- jupyter_ext/team_access.py +105 -0
- jupyter_ext/tests/__init__.py +1 -0
- jupyter_ext/tests/test_context.py +234 -0
- jupyter_ext/tests/test_factory.py +563 -0
- jupyter_ext/tests/test_file_finder.py +139 -0
- jupyter_ext/tests/test_handlers.py +58 -0
- jupyter_ext/tests/test_mcp_selftest.py +204 -0
- jupyter_ext/tests/test_mermaid_fixer.py +272 -0
- jupyter_ext/tests/test_resource_tiers.py +298 -0
- jupyter_ext/tools/__init__.py +15 -0
- jupyter_ext/tools/base.py +90 -0
- jupyter_ext/tools/context.py +268 -0
- jupyter_ext/tools/dir_complete.py +86 -0
- jupyter_ext/tools/file_finder.py +303 -0
- jupyter_ext/tools/file_tools.py +244 -0
- jupyter_ext/tools/jupyter_tools.py +296 -0
- jupyter_ext/tools/registry.py +159 -0
- jupyter_ext/tools/search_tools.py +230 -0
- jupyter_ext/tools/security_tools.py +657 -0
- jupyter_ext/tools/shell_tools.py +109 -0
- jupyter_ext/tools/user_tools.py +132 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from jupyter_ext.handlers import (
|
|
4
|
+
DEFAULT_EXECUTE_COMMAND_TIMEOUT_MS,
|
|
5
|
+
_append_stream_output,
|
|
6
|
+
_resolve_path_in_workspace,
|
|
7
|
+
_resolve_stream_timeout_ms,
|
|
8
|
+
_resolve_timeout_ms,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_resolve_path_strips_duplicate_cwd_prefix() -> None:
|
|
13
|
+
workspace_root = "/workspace"
|
|
14
|
+
requested_cwd = os.path.join(workspace_root, "extensions", "jupyter")
|
|
15
|
+
path = os.path.join("extensions", "jupyter", "cal.py")
|
|
16
|
+
resolved = _resolve_path_in_workspace(path, workspace_root, requested_cwd)
|
|
17
|
+
assert resolved == os.path.join(requested_cwd, "cal.py")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_resolve_path_relative_to_cwd() -> None:
|
|
21
|
+
workspace_root = "/workspace"
|
|
22
|
+
requested_cwd = os.path.join(workspace_root, "extensions", "jupyter")
|
|
23
|
+
resolved = _resolve_path_in_workspace("cal.py", workspace_root, requested_cwd)
|
|
24
|
+
assert resolved == os.path.join(requested_cwd, "cal.py")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_resolve_timeout_ms_uses_default_on_invalid() -> None:
|
|
28
|
+
assert _resolve_timeout_ms(None) == DEFAULT_EXECUTE_COMMAND_TIMEOUT_MS
|
|
29
|
+
assert _resolve_timeout_ms("not-a-number") == DEFAULT_EXECUTE_COMMAND_TIMEOUT_MS
|
|
30
|
+
assert _resolve_timeout_ms(-1) == DEFAULT_EXECUTE_COMMAND_TIMEOUT_MS
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_resolve_timeout_ms_accepts_value() -> None:
|
|
34
|
+
assert _resolve_timeout_ms(120000) == 120000
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_resolve_stream_timeout_ms_uses_default_on_invalid() -> None:
|
|
38
|
+
assert _resolve_stream_timeout_ms(None) == DEFAULT_EXECUTE_COMMAND_TIMEOUT_MS
|
|
39
|
+
assert (
|
|
40
|
+
_resolve_stream_timeout_ms("not-a-number") == DEFAULT_EXECUTE_COMMAND_TIMEOUT_MS
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_resolve_stream_timeout_ms_disables_on_non_positive() -> None:
|
|
45
|
+
assert _resolve_stream_timeout_ms(0) is None
|
|
46
|
+
assert _resolve_stream_timeout_ms(-1) is None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_append_stream_output_appends() -> None:
|
|
50
|
+
output, truncated = _append_stream_output("abc", "def", max_bytes=10)
|
|
51
|
+
assert output == "abcdef"
|
|
52
|
+
assert truncated is False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_append_stream_output_truncates() -> None:
|
|
56
|
+
output, truncated = _append_stream_output("abc", "def", max_bytes=5)
|
|
57
|
+
assert output == "abcde"
|
|
58
|
+
assert truncated is True
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""`/mcp_test` 백엔드 헬퍼 테스트.
|
|
2
|
+
|
|
3
|
+
bdp_mcp 는 이 패키지의 의존성이 아니므로(별도 venv 설치, 3.9/3.10 요구 충돌),
|
|
4
|
+
여기서는 **설치 여부와 무관하게 도는** 순수 함수만 검증한다.
|
|
5
|
+
실제 프로토콜 왕복은 bdp_mcp 쪽 selftest 가 본다.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from jupyter_ext.http_handlers import (
|
|
15
|
+
_kill_quietly,
|
|
16
|
+
_terminate_then_kill,
|
|
17
|
+
_BDP_MCP_PYTHON_ENV,
|
|
18
|
+
_bdp_mcp_python_candidates,
|
|
19
|
+
_build_selftest_cmd,
|
|
20
|
+
_parse_selftest_output,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_cmd_omits_transport_when_both() -> None:
|
|
25
|
+
"""both 는 상류 기본값이라 넘기지 않는다 — 넘기면 인자만 늘고 의미는 같다."""
|
|
26
|
+
cmd = _build_selftest_cmd("/x/python", "both")
|
|
27
|
+
assert cmd == ["/x/python", "-m", "awslabs.bdp_mcp_server.selftest", "--json"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_cmd_includes_transport_and_port() -> None:
|
|
31
|
+
cmd = _build_selftest_cmd("/x/python", "streamable-http", 18099)
|
|
32
|
+
assert cmd[-4:] == ["--transport", "streamable-http", "--port", "18099"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_cmd_uses_module_not_console_script() -> None:
|
|
36
|
+
"""콘솔 스크립트는 launchctl/nohup 기동 시 PATH 에 없을 수 있다."""
|
|
37
|
+
cmd = _build_selftest_cmd(sys.executable)
|
|
38
|
+
assert cmd[0] == sys.executable
|
|
39
|
+
assert cmd[1] == "-m"
|
|
40
|
+
assert "bdp-mcp-selftest" not in cmd
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_parse_reads_stdout_only() -> None:
|
|
44
|
+
"""stderr 에 경고가 섞여도 판정은 stdout 으로만 한다.
|
|
45
|
+
|
|
46
|
+
stderr 를 섞어 파싱하면 멀쩡한 결과를 "깨진 출력" 으로 오판한다.
|
|
47
|
+
"""
|
|
48
|
+
report = {
|
|
49
|
+
"ok": True,
|
|
50
|
+
"transports": [{"transport": "stdio", "ok": True, "checks": []}],
|
|
51
|
+
}
|
|
52
|
+
parsed = _parse_selftest_output(
|
|
53
|
+
json.dumps(report).encode(),
|
|
54
|
+
b"IncompleteFieldDefinitionWarning: Field 'lifespan' ...",
|
|
55
|
+
0,
|
|
56
|
+
)
|
|
57
|
+
assert parsed["report"]["ok"] is True
|
|
58
|
+
assert "error" not in parsed
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_parse_reports_empty_output() -> None:
|
|
62
|
+
parsed = _parse_selftest_output(b"", b"boom", 1)
|
|
63
|
+
assert "error" in parsed
|
|
64
|
+
assert parsed["stderr"] == "boom"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_parse_reports_non_json_stdout() -> None:
|
|
68
|
+
"""JSON 이 아니면 원문 꼬리를 남긴다 — 원인을 못 보면 진단이 아니다."""
|
|
69
|
+
parsed = _parse_selftest_output(b"Traceback (most recent call last):", b"", 1)
|
|
70
|
+
assert "error" in parsed
|
|
71
|
+
assert "Traceback" in parsed["stdout"]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_parse_rejects_unexpected_schema() -> None:
|
|
75
|
+
"""상류 스키마가 바뀌면 조용히 통과시키지 않는다."""
|
|
76
|
+
parsed = _parse_selftest_output(b'{"ok": true}', b"", 0)
|
|
77
|
+
assert "error" in parsed
|
|
78
|
+
assert "transports" in parsed["error"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_candidates_prefer_env_var(monkeypatch) -> None:
|
|
82
|
+
monkeypatch.setenv(_BDP_MCP_PYTHON_ENV, "/custom/venv/bin/python")
|
|
83
|
+
assert _bdp_mcp_python_candidates()[0] == "/custom/venv/bin/python"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_candidates_fall_back_without_env(monkeypatch) -> None:
|
|
87
|
+
"""환경변수가 없으면 ~/repo/bdp_mcp/.venv 관습 → 서버 venv 순."""
|
|
88
|
+
monkeypatch.delenv(_BDP_MCP_PYTHON_ENV, raising=False)
|
|
89
|
+
candidates = _bdp_mcp_python_candidates()
|
|
90
|
+
assert candidates[0].endswith("repo/bdp_mcp/.venv/bin/python")
|
|
91
|
+
assert candidates[-1] == sys.executable
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_candidates_deduplicate(monkeypatch) -> None:
|
|
95
|
+
"""환경변수가 서버 venv 와 같으면 같은 인터프리터를 두 번 재지 않는다."""
|
|
96
|
+
monkeypatch.setenv(_BDP_MCP_PYTHON_ENV, sys.executable)
|
|
97
|
+
candidates = _bdp_mcp_python_candidates()
|
|
98
|
+
assert candidates.count(sys.executable) == 1
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_candidates_expand_tilde_in_env(monkeypatch) -> None:
|
|
102
|
+
"""`~/...` 를 확장하지 않으면 조용히 다음 후보로 폴백해 환경변수가 무시된다.
|
|
103
|
+
|
|
104
|
+
진단 도구에서 "왜 내 설정이 안 먹나" 가 묻히는 것은 나쁜 실패다.
|
|
105
|
+
"""
|
|
106
|
+
monkeypatch.setenv(_BDP_MCP_PYTHON_ENV, "~/myvenv/bin/python")
|
|
107
|
+
first = _bdp_mcp_python_candidates()[0]
|
|
108
|
+
assert "~" not in first
|
|
109
|
+
assert first.endswith("/myvenv/bin/python")
|
|
110
|
+
assert os.path.isabs(first)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class _AlreadyGoneProc:
|
|
114
|
+
"""returncode 확인과 시그널 사이에 스스로 끝난 프로세스."""
|
|
115
|
+
|
|
116
|
+
returncode = None
|
|
117
|
+
|
|
118
|
+
def kill(self):
|
|
119
|
+
raise ProcessLookupError
|
|
120
|
+
|
|
121
|
+
def terminate(self):
|
|
122
|
+
raise ProcessLookupError
|
|
123
|
+
|
|
124
|
+
async def wait(self): # pragma: no cover - 도달하지 않아야 한다
|
|
125
|
+
raise AssertionError("이미 죽은 프로세스를 기다리면 안 된다")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_kill_quietly_survives_process_lookup_error() -> None:
|
|
129
|
+
"""ProcessLookupError 가 except 블록 안에서 터지면 응답 없는 500 이 된다.
|
|
130
|
+
|
|
131
|
+
진단 도구가 진단 불가로 죽는 형태라 반드시 삼켜야 한다.
|
|
132
|
+
"""
|
|
133
|
+
asyncio.run(_kill_quietly(_AlreadyGoneProc()))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_terminate_then_kill_survives_process_lookup_error() -> None:
|
|
137
|
+
asyncio.run(_terminate_then_kill(_AlreadyGoneProc()))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_kill_helpers_ignore_finished_process() -> None:
|
|
141
|
+
class _Done:
|
|
142
|
+
returncode = 0
|
|
143
|
+
|
|
144
|
+
asyncio.run(_kill_quietly(_Done()))
|
|
145
|
+
asyncio.run(_terminate_then_kill(_Done()))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_terminate_then_kill_prefers_sigterm() -> None:
|
|
149
|
+
"""SIGKILL 로 바로 죽이면 selftest 의 finally 가 못 돌아 손자 MCP 서버가
|
|
150
|
+
고아로 남아 포트를 계속 점유한다. SIGTERM 을 먼저 준다."""
|
|
151
|
+
calls = []
|
|
152
|
+
|
|
153
|
+
class _Proc:
|
|
154
|
+
returncode = None
|
|
155
|
+
|
|
156
|
+
def terminate(self):
|
|
157
|
+
calls.append("terminate")
|
|
158
|
+
self.returncode = -15
|
|
159
|
+
|
|
160
|
+
def kill(self): # pragma: no cover - 여기 오면 안 된다
|
|
161
|
+
calls.append("kill")
|
|
162
|
+
|
|
163
|
+
async def wait(self):
|
|
164
|
+
return self.returncode
|
|
165
|
+
|
|
166
|
+
asyncio.run(_terminate_then_kill(_Proc()))
|
|
167
|
+
assert calls == ["terminate"]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_terminate_then_kill_escalates_when_sigterm_ignored() -> None:
|
|
171
|
+
calls = []
|
|
172
|
+
|
|
173
|
+
class _Stubborn:
|
|
174
|
+
returncode = None
|
|
175
|
+
|
|
176
|
+
def terminate(self):
|
|
177
|
+
calls.append("terminate")
|
|
178
|
+
|
|
179
|
+
def kill(self):
|
|
180
|
+
calls.append("kill")
|
|
181
|
+
self.returncode = -9
|
|
182
|
+
|
|
183
|
+
async def wait(self):
|
|
184
|
+
if self.returncode is None:
|
|
185
|
+
await asyncio.sleep(10) # SIGTERM 을 무시하는 프로세스
|
|
186
|
+
return self.returncode
|
|
187
|
+
|
|
188
|
+
asyncio.run(_terminate_then_kill(_Stubborn(), grace=0.05))
|
|
189
|
+
assert calls == ["terminate", "kill"]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_cmd_adds_call_flag() -> None:
|
|
193
|
+
"""`--call` 은 필수 인자 없는 도구를 실제로 호출한다 (실 AWS)."""
|
|
194
|
+
assert _build_selftest_cmd("/x/python", call_tools=True)[-1] == "--call"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_cmd_omits_call_flag_by_default() -> None:
|
|
198
|
+
"""기본은 호출하지 않는다 — 자격 없는 환경에서 늘 빨간불이 되면 안 된다."""
|
|
199
|
+
assert "--call" not in _build_selftest_cmd("/x/python")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_cmd_combines_transport_port_and_call() -> None:
|
|
203
|
+
cmd = _build_selftest_cmd("/x/python", "streamable-http", 18099, True)
|
|
204
|
+
assert cmd[-5:] == ["--transport", "streamable-http", "--port", "18099", "--call"]
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Tests for mermaid_fixer — deterministic Mermaid syntax repair."""
|
|
2
|
+
|
|
3
|
+
from jupyter_ext.agent.mermaid_fixer import fix_mermaid_blocks, fix_mermaid_code
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# ---------------------------------------------------------------------------
|
|
7
|
+
# Category 1: Comments
|
|
8
|
+
# ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_fix_comments():
|
|
12
|
+
code = "graph TD\n A --> B %% this is a comment\n B --> C"
|
|
13
|
+
result = fix_mermaid_code(code)
|
|
14
|
+
assert "%%" not in result
|
|
15
|
+
assert "A --> B" in result
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_fix_comments_standalone_line():
|
|
19
|
+
code = "graph TD\n %% full line comment\n A --> B"
|
|
20
|
+
result = fix_mermaid_code(code)
|
|
21
|
+
assert "%%" not in result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Category 2: Reserved keyword `end`
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_fix_end_keyword_as_node():
|
|
30
|
+
code = "graph TD\n start --> end"
|
|
31
|
+
result = fix_mermaid_code(code)
|
|
32
|
+
assert "end_node" in result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_fix_end_preserves_subgraph_closer():
|
|
36
|
+
code = "graph TD\n subgraph sub1\n A --> B\n end"
|
|
37
|
+
result = fix_mermaid_code(code)
|
|
38
|
+
# The standalone `end` (subgraph closer) must remain
|
|
39
|
+
assert result.strip().endswith("end")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_fix_end_preserves_directive():
|
|
43
|
+
code = "graph TD\n class end myClass"
|
|
44
|
+
result = fix_mermaid_code(code)
|
|
45
|
+
# `end` inside a class directive must remain
|
|
46
|
+
assert "class end" in result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Category 3: Escaped quotes
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_fix_escaped_quotes():
|
|
55
|
+
code = 'A["hello \\"world\\""]'
|
|
56
|
+
result = fix_mermaid_code(code)
|
|
57
|
+
assert '\\"' not in result
|
|
58
|
+
assert """ in result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Category 4: Arrow syntax
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_fix_single_arrow():
|
|
67
|
+
code = "graph TD\n A -> B"
|
|
68
|
+
result = fix_mermaid_code(code)
|
|
69
|
+
assert " --> " in result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_fix_dotted_arrow():
|
|
73
|
+
code = "graph TD\n A -. B"
|
|
74
|
+
result = fix_mermaid_code(code)
|
|
75
|
+
assert "-.-> " in result
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_double_arrow_unchanged():
|
|
79
|
+
code = "graph TD\n A --> B"
|
|
80
|
+
result = fix_mermaid_code(code)
|
|
81
|
+
assert " --> " in result
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
# Category 5: Nested brackets
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_fix_nested_brackets():
|
|
90
|
+
code = 'graph TD\n H["dask[dataframe] 설치"]'
|
|
91
|
+
result = fix_mermaid_code(code)
|
|
92
|
+
assert "[" in result
|
|
93
|
+
assert "]" in result
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_fix_misquoted_labels():
|
|
97
|
+
"""Misquoted: opening " but no matching closing "."""
|
|
98
|
+
code = "graph TD\n E[\"rcParams['family'\"] 설정]"
|
|
99
|
+
result = fix_mermaid_code(code)
|
|
100
|
+
# Should handle gracefully without crashing
|
|
101
|
+
assert "E[" in result
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# Category 6: Node labels
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_fix_unquoted_node_label():
|
|
110
|
+
code = "graph TD\n A[Hello World]"
|
|
111
|
+
result = fix_mermaid_code(code)
|
|
112
|
+
assert '["Hello World"]' in result
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_fix_already_quoted_label():
|
|
116
|
+
code = 'graph TD\n A["Hello World"]'
|
|
117
|
+
result = fix_mermaid_code(code)
|
|
118
|
+
assert '["Hello World"]' in result
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_fix_subroutine_label():
|
|
122
|
+
code = "graph TD\n A[[My Process]]"
|
|
123
|
+
result = fix_mermaid_code(code)
|
|
124
|
+
assert '[["My Process"]]' in result
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Category 7: Edge labels
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_fix_edge_labels_with_brackets():
|
|
133
|
+
code = "graph TD\n A -->|[text]| B"
|
|
134
|
+
result = fix_mermaid_code(code)
|
|
135
|
+
assert "[" in result
|
|
136
|
+
assert "]" in result
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_edge_labels_no_brackets_unchanged():
|
|
140
|
+
code = "graph TD\n A -->|yes| B"
|
|
141
|
+
result = fix_mermaid_code(code)
|
|
142
|
+
assert "|yes|" in result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Category 8: Subgraph titles
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_fix_subgraph_unquoted_title():
|
|
151
|
+
code = "subgraph My Section\n A --> B\nend"
|
|
152
|
+
result = fix_mermaid_code(code)
|
|
153
|
+
assert '"My Section"' in result
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_subgraph_quoted_title_unchanged():
|
|
157
|
+
code = 'subgraph "My Section"\n A --> B\nend'
|
|
158
|
+
result = fix_mermaid_code(code)
|
|
159
|
+
assert '"My Section"' in result
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Category 9: Empty labels
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_fix_empty_labels():
|
|
168
|
+
code = 'graph TD\n A[""] --> B'
|
|
169
|
+
result = fix_mermaid_code(code)
|
|
170
|
+
assert '[""]' not in result
|
|
171
|
+
assert "A --> B" in result
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Category 10: Direction
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_fix_invalid_direction():
|
|
180
|
+
code = "graph TD\n direction XX\n A --> B"
|
|
181
|
+
result = fix_mermaid_code(code)
|
|
182
|
+
assert "direction TD" in result
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_valid_direction_unchanged():
|
|
186
|
+
for d in ("TB", "BT", "LR", "RL", "TD"):
|
|
187
|
+
code = f"graph TD\n direction {d}\n A --> B"
|
|
188
|
+
result = fix_mermaid_code(code)
|
|
189
|
+
assert f"direction {d}" in result
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# Category 11: Trailing semicolons
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_fix_trailing_semicolons():
|
|
198
|
+
code = "graph TD\n A --> B;\n B --> C;"
|
|
199
|
+
result = fix_mermaid_code(code)
|
|
200
|
+
assert ";" not in result
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_no_semicolons_unchanged():
|
|
204
|
+
code = "graph TD\n A --> B\n B --> C"
|
|
205
|
+
result = fix_mermaid_code(code)
|
|
206
|
+
assert result.strip() == code.strip()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
# Category 12: Duplicate edges
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_fix_duplicate_edges():
|
|
215
|
+
code = "graph TD\n A --> B\n A --> B\n B --> C"
|
|
216
|
+
result = fix_mermaid_code(code)
|
|
217
|
+
assert result.count("A --> B") == 1
|
|
218
|
+
assert "B --> C" in result
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_different_edges_preserved():
|
|
222
|
+
code = "graph TD\n A --> B\n B --> C"
|
|
223
|
+
result = fix_mermaid_code(code)
|
|
224
|
+
assert "A --> B" in result
|
|
225
|
+
assert "B --> C" in result
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Integration: fix_mermaid_blocks
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_no_mermaid_passthrough():
|
|
234
|
+
text = "Hello world, no mermaid here."
|
|
235
|
+
assert fix_mermaid_blocks(text) == text
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_multiple_blocks():
|
|
239
|
+
text = (
|
|
240
|
+
"Some text\n"
|
|
241
|
+
"```mermaid\n"
|
|
242
|
+
"graph TD\n A -> B\n"
|
|
243
|
+
"```\n"
|
|
244
|
+
"Middle text\n"
|
|
245
|
+
"```mermaid\n"
|
|
246
|
+
"graph LR\n C -> D\n"
|
|
247
|
+
"```\n"
|
|
248
|
+
"End text"
|
|
249
|
+
)
|
|
250
|
+
result = fix_mermaid_blocks(text)
|
|
251
|
+
assert result.count("-->") == 2
|
|
252
|
+
assert "Some text" in result
|
|
253
|
+
assert "Middle text" in result
|
|
254
|
+
assert "End text" in result
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_composition_multiple_issues():
|
|
258
|
+
"""Multiple issues coexist in a single block."""
|
|
259
|
+
code = (
|
|
260
|
+
"graph TD\n"
|
|
261
|
+
" %% comment\n"
|
|
262
|
+
" start --> end;\n"
|
|
263
|
+
" A[Hello World] -> B\n"
|
|
264
|
+
" direction XX\n"
|
|
265
|
+
)
|
|
266
|
+
result = fix_mermaid_code(code)
|
|
267
|
+
assert "%%" not in result
|
|
268
|
+
assert "end_node" in result
|
|
269
|
+
assert ";" not in result
|
|
270
|
+
assert " --> " in result # single arrow fixed
|
|
271
|
+
assert "direction TD" in result
|
|
272
|
+
assert '["Hello World"]' in result
|