codexa 0.4.0__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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- semantic_code_intelligence/workspace/__init__.py +427 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""Tests for Phase 12 — Platform Enhancements.
|
|
2
|
+
|
|
3
|
+
Covers: new plugin hooks (ON_STREAM, CUSTOM_VALIDATION), reasoning engine
|
|
4
|
+
improvements (context pruning, priority scoring, explainability), enhanced
|
|
5
|
+
security validator patterns, and VSCode streaming context.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
from unittest.mock import MagicMock, patch
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
# =========================================================================
|
|
19
|
+
# Plugin hook tests
|
|
20
|
+
# =========================================================================
|
|
21
|
+
|
|
22
|
+
from semantic_code_intelligence.plugins import (
|
|
23
|
+
PluginBase,
|
|
24
|
+
PluginHook,
|
|
25
|
+
PluginManager,
|
|
26
|
+
PluginMetadata,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestNewPluginHooks:
|
|
31
|
+
def test_on_stream_exists(self):
|
|
32
|
+
assert PluginHook.ON_STREAM == "on_stream"
|
|
33
|
+
|
|
34
|
+
def test_custom_validation_exists(self):
|
|
35
|
+
assert PluginHook.CUSTOM_VALIDATION == "custom_validation"
|
|
36
|
+
|
|
37
|
+
def test_all_hooks_count(self):
|
|
38
|
+
# 11 original + 2 new = 13
|
|
39
|
+
assert len(PluginHook) == 22
|
|
40
|
+
|
|
41
|
+
def test_hook_registry_has_new_hooks(self):
|
|
42
|
+
mgr = PluginManager()
|
|
43
|
+
assert PluginHook.ON_STREAM in mgr._hook_registry
|
|
44
|
+
assert PluginHook.CUSTOM_VALIDATION in mgr._hook_registry
|
|
45
|
+
|
|
46
|
+
def test_dispatch_on_stream(self):
|
|
47
|
+
"""ON_STREAM hook should dispatch to active plugins."""
|
|
48
|
+
|
|
49
|
+
class StreamPlugin(PluginBase):
|
|
50
|
+
def metadata(self):
|
|
51
|
+
return PluginMetadata(name="streamer", hooks=[PluginHook.ON_STREAM])
|
|
52
|
+
|
|
53
|
+
def on_hook(self, hook, data):
|
|
54
|
+
data["streamed"] = True
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
mgr = PluginManager()
|
|
58
|
+
plugin = StreamPlugin()
|
|
59
|
+
mgr.register(plugin)
|
|
60
|
+
mgr.activate("streamer")
|
|
61
|
+
result = mgr.dispatch(PluginHook.ON_STREAM, {"token": "hello"})
|
|
62
|
+
assert result["streamed"] is True
|
|
63
|
+
assert result["token"] == "hello"
|
|
64
|
+
|
|
65
|
+
def test_dispatch_custom_validation(self):
|
|
66
|
+
"""CUSTOM_VALIDATION hook should allow custom validation rules."""
|
|
67
|
+
|
|
68
|
+
class ValidatorPlugin(PluginBase):
|
|
69
|
+
def metadata(self):
|
|
70
|
+
return PluginMetadata(name="validator", hooks=[PluginHook.CUSTOM_VALIDATION])
|
|
71
|
+
|
|
72
|
+
def on_hook(self, hook, data):
|
|
73
|
+
code = data.get("code", "")
|
|
74
|
+
issues = data.get("issues", [])
|
|
75
|
+
if "TODO" in code:
|
|
76
|
+
issues.append({"description": "TODO found", "severity": "info"})
|
|
77
|
+
data["issues"] = issues
|
|
78
|
+
return data
|
|
79
|
+
|
|
80
|
+
mgr = PluginManager()
|
|
81
|
+
plugin = ValidatorPlugin()
|
|
82
|
+
mgr.register(plugin)
|
|
83
|
+
mgr.activate("validator")
|
|
84
|
+
result = mgr.dispatch(PluginHook.CUSTOM_VALIDATION, {"code": "x = 1 # TODO fix", "issues": []})
|
|
85
|
+
assert len(result["issues"]) == 1
|
|
86
|
+
assert result["issues"][0]["description"] == "TODO found"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =========================================================================
|
|
90
|
+
# Reasoning engine tests
|
|
91
|
+
# =========================================================================
|
|
92
|
+
|
|
93
|
+
from semantic_code_intelligence.llm.reasoning import (
|
|
94
|
+
AskResult,
|
|
95
|
+
ReasoningEngine,
|
|
96
|
+
RefactorResult,
|
|
97
|
+
ReviewResult,
|
|
98
|
+
SuggestResult,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestExplainabilityMetadata:
|
|
103
|
+
def test_ask_result_has_explainability(self):
|
|
104
|
+
r = AskResult(question="q", answer="a", explainability={"method": "test"})
|
|
105
|
+
d = r.to_dict()
|
|
106
|
+
assert "explainability" in d
|
|
107
|
+
assert d["explainability"]["method"] == "test"
|
|
108
|
+
|
|
109
|
+
def test_review_result_has_explainability(self):
|
|
110
|
+
r = ReviewResult(file_path="f.py", explainability={"x": 1})
|
|
111
|
+
assert r.to_dict()["explainability"] == {"x": 1}
|
|
112
|
+
|
|
113
|
+
def test_refactor_result_has_explainability(self):
|
|
114
|
+
r = RefactorResult(file_path="f.py", explainability={"x": 2})
|
|
115
|
+
assert r.to_dict()["explainability"] == {"x": 2}
|
|
116
|
+
|
|
117
|
+
def test_suggest_result_has_explainability(self):
|
|
118
|
+
r = SuggestResult(target="t", explainability={"x": 3})
|
|
119
|
+
assert r.to_dict()["explainability"] == {"x": 3}
|
|
120
|
+
|
|
121
|
+
def test_default_empty_explainability(self):
|
|
122
|
+
r = AskResult(question="q", answer="a")
|
|
123
|
+
assert r.explainability == {}
|
|
124
|
+
assert r.to_dict()["explainability"] == {}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestContextPruning:
|
|
128
|
+
def test_score_snippet_base(self):
|
|
129
|
+
snip = {"score": 0.85, "content": "def hello(): pass"}
|
|
130
|
+
score = ReasoningEngine._score_snippet(snip, "hello function")
|
|
131
|
+
assert score >= 0.85
|
|
132
|
+
|
|
133
|
+
def test_score_snippet_keyword_bonus(self):
|
|
134
|
+
snip = {"score": 0.5, "content": "def authenticate_user(username): pass"}
|
|
135
|
+
score_relevant = ReasoningEngine._score_snippet(snip, "authenticate user")
|
|
136
|
+
score_irrelevant = ReasoningEngine._score_snippet(snip, "database migration")
|
|
137
|
+
assert score_relevant > score_irrelevant
|
|
138
|
+
|
|
139
|
+
def test_score_snippet_no_content(self):
|
|
140
|
+
snip = {"score": 0.3, "content": ""}
|
|
141
|
+
score = ReasoningEngine._score_snippet(snip, "query")
|
|
142
|
+
assert score == 0.3
|
|
143
|
+
|
|
144
|
+
def test_prune_context_limits_chars(self):
|
|
145
|
+
provider = MagicMock()
|
|
146
|
+
engine = ReasoningEngine(provider, Path("."), max_context_chars=100)
|
|
147
|
+
snippets = [
|
|
148
|
+
{"score": 0.9, "content": "A" * 60},
|
|
149
|
+
{"score": 0.8, "content": "B" * 60},
|
|
150
|
+
{"score": 0.7, "content": "C" * 60},
|
|
151
|
+
]
|
|
152
|
+
pruned = engine._prune_context(snippets, "query")
|
|
153
|
+
total = sum(len(s["content"]) for s in pruned)
|
|
154
|
+
assert total <= 120 # first snippet always kept; second may push over
|
|
155
|
+
|
|
156
|
+
def test_prune_context_preserves_order(self):
|
|
157
|
+
provider = MagicMock()
|
|
158
|
+
engine = ReasoningEngine(provider, Path("."), max_context_chars=10000)
|
|
159
|
+
snippets = [
|
|
160
|
+
{"score": 0.5, "content": "low"},
|
|
161
|
+
{"score": 0.9, "content": "high"},
|
|
162
|
+
{"score": 0.7, "content": "mid"},
|
|
163
|
+
]
|
|
164
|
+
pruned = engine._prune_context(snippets, "test")
|
|
165
|
+
scores = [s.get("priority_score", 0) for s in pruned]
|
|
166
|
+
assert scores == sorted(scores, reverse=True)
|
|
167
|
+
|
|
168
|
+
def test_prune_context_adds_priority_score(self):
|
|
169
|
+
provider = MagicMock()
|
|
170
|
+
engine = ReasoningEngine(provider, Path("."))
|
|
171
|
+
snippets = [{"score": 0.6, "content": "test data here"}]
|
|
172
|
+
pruned = engine._prune_context(snippets, "test")
|
|
173
|
+
assert "priority_score" in pruned[0]
|
|
174
|
+
|
|
175
|
+
def test_prune_context_empty(self):
|
|
176
|
+
provider = MagicMock()
|
|
177
|
+
engine = ReasoningEngine(provider, Path("."))
|
|
178
|
+
assert engine._prune_context([], "query") == []
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestReasoningEngineMaxContext:
|
|
182
|
+
def test_default_max_context(self):
|
|
183
|
+
provider = MagicMock()
|
|
184
|
+
engine = ReasoningEngine(provider, Path("."))
|
|
185
|
+
assert engine._max_ctx == ReasoningEngine.DEFAULT_MAX_CONTEXT_CHARS
|
|
186
|
+
|
|
187
|
+
def test_custom_max_context(self):
|
|
188
|
+
provider = MagicMock()
|
|
189
|
+
engine = ReasoningEngine(provider, Path("."), max_context_chars=2000)
|
|
190
|
+
assert engine._max_ctx == 2000
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# =========================================================================
|
|
194
|
+
# Security validator tests
|
|
195
|
+
# =========================================================================
|
|
196
|
+
|
|
197
|
+
from semantic_code_intelligence.llm.safety import SafetyValidator
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class TestEnhancedSafetyPatterns:
|
|
201
|
+
def setup_method(self):
|
|
202
|
+
self.validator = SafetyValidator()
|
|
203
|
+
|
|
204
|
+
# Original patterns still work
|
|
205
|
+
def test_os_system(self):
|
|
206
|
+
assert not self.validator.is_safe("os.system('ls')")
|
|
207
|
+
|
|
208
|
+
def test_eval(self):
|
|
209
|
+
assert not self.validator.is_safe("result = eval(user_input)")
|
|
210
|
+
|
|
211
|
+
def test_subprocess_shell(self):
|
|
212
|
+
assert not self.validator.is_safe("subprocess.run(cmd, shell=True)")
|
|
213
|
+
|
|
214
|
+
def test_drop_table(self):
|
|
215
|
+
assert not self.validator.is_safe("DROP TABLE users")
|
|
216
|
+
|
|
217
|
+
# New Phase 12 patterns
|
|
218
|
+
def test_path_traversal(self):
|
|
219
|
+
assert not self.validator.is_safe("open('../../etc/passwd')")
|
|
220
|
+
|
|
221
|
+
def test_hardcoded_password(self):
|
|
222
|
+
assert not self.validator.is_safe('password = "mysecretpassword123"')
|
|
223
|
+
|
|
224
|
+
def test_hardcoded_api_key(self):
|
|
225
|
+
assert not self.validator.is_safe('api_key = "sk-proj-1234567890abcdef"')
|
|
226
|
+
|
|
227
|
+
def test_innerhtml_xss(self):
|
|
228
|
+
assert not self.validator.is_safe('element.innerHTML = userInput')
|
|
229
|
+
|
|
230
|
+
def test_document_write_xss(self):
|
|
231
|
+
assert not self.validator.is_safe('document.write(data)')
|
|
232
|
+
|
|
233
|
+
def test_md5_insecure(self):
|
|
234
|
+
assert not self.validator.is_safe("hash = MD5(data)")
|
|
235
|
+
|
|
236
|
+
def test_sha1_insecure(self):
|
|
237
|
+
assert not self.validator.is_safe("hash = sha1(data)")
|
|
238
|
+
|
|
239
|
+
def test_http_insecure(self):
|
|
240
|
+
assert not self.validator.is_safe("url = 'http://example.com/api'")
|
|
241
|
+
|
|
242
|
+
def test_http_localhost_allowed(self):
|
|
243
|
+
assert self.validator.is_safe("url = 'http://localhost:8080'")
|
|
244
|
+
|
|
245
|
+
def test_ssl_verify_disabled(self):
|
|
246
|
+
assert not self.validator.is_safe("requests.get(url, verify=False)")
|
|
247
|
+
|
|
248
|
+
def test_safe_code_passes(self):
|
|
249
|
+
safe = """
|
|
250
|
+
import hashlib
|
|
251
|
+
from pathlib import Path
|
|
252
|
+
|
|
253
|
+
def get_hash(data: bytes) -> str:
|
|
254
|
+
return hashlib.sha256(data).hexdigest()
|
|
255
|
+
|
|
256
|
+
config = Path("config.yaml").read_text()
|
|
257
|
+
"""
|
|
258
|
+
assert self.validator.is_safe(safe)
|
|
259
|
+
|
|
260
|
+
def test_validate_report_details(self):
|
|
261
|
+
report = self.validator.validate("el.innerHTML = x\nMD5(y)")
|
|
262
|
+
assert not report.safe
|
|
263
|
+
assert len(report.issues) >= 2
|
|
264
|
+
descs = [i.description for i in report.issues]
|
|
265
|
+
assert any("XSS" in d for d in descs)
|
|
266
|
+
assert any("MD5" in d for d in descs)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# =========================================================================
|
|
270
|
+
# VSCode streaming context tests
|
|
271
|
+
# =========================================================================
|
|
272
|
+
|
|
273
|
+
from semantic_code_intelligence.bridge.vscode import (
|
|
274
|
+
StreamChunk,
|
|
275
|
+
VSCodeBridge,
|
|
276
|
+
build_streaming_context,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class TestStreamChunk:
|
|
281
|
+
def test_to_dict(self):
|
|
282
|
+
chunk = StreamChunk(kind="token", content="hello")
|
|
283
|
+
d = chunk.to_dict()
|
|
284
|
+
assert d["kind"] == "token"
|
|
285
|
+
assert d["content"] == "hello"
|
|
286
|
+
assert d["metadata"] == {}
|
|
287
|
+
|
|
288
|
+
def test_to_dict_with_metadata(self):
|
|
289
|
+
chunk = StreamChunk(kind="context", content="info", metadata={"count": 3})
|
|
290
|
+
d = chunk.to_dict()
|
|
291
|
+
assert d["metadata"]["count"] == 3
|
|
292
|
+
|
|
293
|
+
def test_to_sse(self):
|
|
294
|
+
chunk = StreamChunk(kind="done", content="")
|
|
295
|
+
sse = chunk.to_sse()
|
|
296
|
+
assert sse.startswith("data: ")
|
|
297
|
+
assert sse.endswith("\n\n")
|
|
298
|
+
parsed = json.loads(sse[6:].strip())
|
|
299
|
+
assert parsed["kind"] == "done"
|
|
300
|
+
|
|
301
|
+
def test_kind_values(self):
|
|
302
|
+
for kind in ("token", "context", "done", "error"):
|
|
303
|
+
chunk = StreamChunk(kind=kind)
|
|
304
|
+
assert chunk.kind == kind
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class TestBuildStreamingContext:
|
|
308
|
+
def test_basic_structure(self):
|
|
309
|
+
provider = MagicMock()
|
|
310
|
+
provider.context_for_query.return_value = {
|
|
311
|
+
"results": [
|
|
312
|
+
{"content": "def foo(): pass", "file_path": "a.py", "score": 0.9},
|
|
313
|
+
{"content": "def bar(): pass", "file_path": "b.py", "score": 0.7},
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
chunks = build_streaming_context("test query", provider, top_k=5)
|
|
317
|
+
# First chunk should be context
|
|
318
|
+
assert chunks[0].kind == "context"
|
|
319
|
+
assert "2" in chunks[0].content # "Found 2 relevant snippets"
|
|
320
|
+
# Middle chunks should be tokens
|
|
321
|
+
assert chunks[1].kind == "token"
|
|
322
|
+
assert chunks[2].kind == "token"
|
|
323
|
+
# Last chunk should be done
|
|
324
|
+
assert chunks[-1].kind == "done"
|
|
325
|
+
|
|
326
|
+
def test_empty_results(self):
|
|
327
|
+
provider = MagicMock()
|
|
328
|
+
provider.context_for_query.return_value = {"results": []}
|
|
329
|
+
chunks = build_streaming_context("nothing", provider)
|
|
330
|
+
assert len(chunks) == 2 # context + done
|
|
331
|
+
assert chunks[0].kind == "context"
|
|
332
|
+
assert chunks[1].kind == "done"
|
|
333
|
+
|
|
334
|
+
def test_token_metadata_has_file_path(self):
|
|
335
|
+
provider = MagicMock()
|
|
336
|
+
provider.context_for_query.return_value = {
|
|
337
|
+
"results": [{"content": "code", "file_path": "x.py", "score": 0.8}]
|
|
338
|
+
}
|
|
339
|
+
chunks = build_streaming_context("q", provider)
|
|
340
|
+
token_chunk = chunks[1]
|
|
341
|
+
assert token_chunk.metadata["file_path"] == "x.py"
|
|
342
|
+
assert token_chunk.metadata["score"] == 0.8
|
|
343
|
+
|
|
344
|
+
def test_all_chunks_serializable(self):
|
|
345
|
+
provider = MagicMock()
|
|
346
|
+
provider.context_for_query.return_value = {
|
|
347
|
+
"results": [{"content": "test", "file_path": "f.py", "score": 0.5}]
|
|
348
|
+
}
|
|
349
|
+
chunks = build_streaming_context("q", provider)
|
|
350
|
+
for chunk in chunks:
|
|
351
|
+
# Must be JSON-serializable
|
|
352
|
+
serialized = json.dumps(chunk.to_dict())
|
|
353
|
+
assert serialized # non-empty
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# =========================================================================
|
|
357
|
+
# Integration: existing Phase 9 features still work
|
|
358
|
+
# =========================================================================
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class TestExistingBridgeFeaturesIntact:
|
|
362
|
+
def test_vscode_bridge_hover_method_exists(self):
|
|
363
|
+
assert hasattr(VSCodeBridge, "hover")
|
|
364
|
+
|
|
365
|
+
def test_vscode_bridge_diagnostics_method_exists(self):
|
|
366
|
+
assert hasattr(VSCodeBridge, "diagnostics")
|
|
367
|
+
|
|
368
|
+
def test_vscode_bridge_completions_method_exists(self):
|
|
369
|
+
assert hasattr(VSCodeBridge, "completions")
|
|
370
|
+
|
|
371
|
+
def test_vscode_bridge_code_actions_method_exists(self):
|
|
372
|
+
assert hasattr(VSCodeBridge, "code_actions")
|
|
373
|
+
|
|
374
|
+
def test_vscode_bridge_file_summary_method_exists(self):
|
|
375
|
+
assert hasattr(VSCodeBridge, "file_summary")
|