Private
Public Access
Merge origin/tier2/module_taxonomy_refactor_20260627: bring in v2 SHIPPED work
Per post_module_taxonomy_de_cruft_20260627 Phase 0 prerequisite. Master is at6344b49f(pre-merge of v2 SHIPPED). This merge brings in the 18 v2 SHIPPED commits that define the destination modules (src.mma, src/project.py, src/project_files.py, src.tool_presets, src.tool_bias, src.external_editor, src.personas, src.workspace_manager, src.mcp_client) needed by the Phase 2 consumer migration in commit8f11340b. Conflicts resolved (all were import-block re-orderings between my migration's update and v2 SHIPPED's update of the same files): - src/external_editor.py: took v2 SHIPPED version (class definitions + the no-alias import pattern) - src/personas.py: took v2 SHIPPED version - src/tool_bias.py: took v2 SHIPPED version - src/tool_presets.py: took v2 SHIPPED version - src/workspace_manager.py: took v2 SHIPPED version - src/ai_client.py: took v2 SHIPPED version (removes the 'as _FIC' alias; uses 'from src.project_files import FileItem' directly per the v2 SHIPPED style) - conductor/tracks/module_taxonomy_refactor_20260627/spec.md: took HEAD version (my Phase 1 VC2 + VC10 corrections; the v2 SHIPPED version was the pre-correction spec)
This commit is contained in:
@@ -20,7 +20,7 @@ from src.result_types import Result
|
||||
from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.ai_client import run_with_tool_loop
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
|
||||
@pytest.fixture
|
||||
def caps() -> VendorCapabilities:
|
||||
|
||||
@@ -11,7 +11,7 @@ from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.ai_client import run_with_tool_loop
|
||||
from src.result_types import Result
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
|
||||
def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse:
|
||||
return NormalizedResponse(
|
||||
|
||||
@@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch
|
||||
from src.openai_compatible import NormalizedResponse
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.ai_client import run_with_tool_loop
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
|
||||
def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse:
|
||||
return NormalizedResponse(
|
||||
|
||||
@@ -214,7 +214,7 @@ def test_fr3_minimax_thinking_in_returned_text() -> None:
|
||||
from src import openai_compatible as oc
|
||||
from src import provider_state
|
||||
from src.provider_state import ProviderHistory
|
||||
from src.vendor_capabilities import register, VendorCapabilities
|
||||
from src.ai_client import register, VendorCapabilities
|
||||
register(VendorCapabilities(vendor="minimax", model="MiniMax-M2.7", reasoning=True))
|
||||
ai_client._model = "MiniMax-M2.7"
|
||||
|
||||
|
||||
@@ -13,24 +13,25 @@ class TestArchBoundaryPhase2(unittest.TestCase):
|
||||
|
||||
def test_toml_exposes_all_dispatch_tools(self) -> None:
|
||||
"""manual_slop.toml [agent.tools] must list every tool in mcp_client.dispatch()."""
|
||||
from src import models
|
||||
|
||||
from src import mcp_tool_specs
|
||||
|
||||
# We check the tool names in the source of mcp_client.dispatch
|
||||
import inspect
|
||||
import src.mcp_client as mcp
|
||||
source = inspect.getsource(mcp.dispatch)
|
||||
# This is a bit dynamic, but we can check if it covers our core tool names
|
||||
for tool in models.AGENT_TOOL_NAMES:
|
||||
for tool in mcp_tool_specs.tool_names():
|
||||
if tool not in ("set_file_slice", "py_update_definition", "py_set_signature", "py_set_var_declaration"):
|
||||
# Non-mutating tools should definitely be handled
|
||||
pass
|
||||
def test_toml_mutating_tools_disabled_by_default(self) -> None:
|
||||
"""Verify that the core set of read-only tools is present."""
|
||||
from src.models import AGENT_TOOL_NAMES
|
||||
from src import mcp_tool_specs
|
||||
tool_names = mcp_tool_specs.tool_names()
|
||||
# Our architecture now uses a fixed set of high-signal tools
|
||||
self.assertIn("read_file", AGENT_TOOL_NAMES)
|
||||
self.assertIn("list_directory", AGENT_TOOL_NAMES)
|
||||
self.assertIn("py_get_skeleton", AGENT_TOOL_NAMES)
|
||||
self.assertIn("read_file", tool_names)
|
||||
self.assertIn("list_directory", tool_names)
|
||||
self.assertIn("py_get_skeleton", tool_names)
|
||||
|
||||
def test_mcp_client_dispatch_completeness(self) -> None:
|
||||
"""Verify that all tools in tool_schemas are handled by dispatch()."""
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
"""Tests for scripts/audit_imports.py (post-2026-06-27).
|
||||
|
||||
Verifies:
|
||||
1. The script flags `from X import Y as _Y` _PREFIX aliasing.
|
||||
2. The script flags `from X import Y` inside a function body (local import).
|
||||
3. The script ALLOWS local imports inside `try/except ImportError:` (optional deps).
|
||||
4. The script respects --strict exit code.
|
||||
5. The script detects repeated .from_dict() calls in the same expression (info only).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
SCRIPT = REPO_ROOT / "scripts" / "audit_imports.py"
|
||||
|
||||
|
||||
def _run_audit(*args: str) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(SCRIPT), *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=REPO_ROOT,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fixture_file(tmp_path: Path):
|
||||
"""Create a temporary .py file with known import patterns for testing."""
|
||||
def _make(content: str, name: str = "test_mod.py") -> Path:
|
||||
f = tmp_path / name
|
||||
f.write_text(textwrap.dedent(content), encoding="utf-8")
|
||||
return f
|
||||
return _make
|
||||
|
||||
|
||||
def test_script_exists():
|
||||
assert SCRIPT.is_file(), f"audit_imports.py missing at {SCRIPT}"
|
||||
|
||||
|
||||
def test_no_local_imports_in_clean_file(fixture_file):
|
||||
f = fixture_file(
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
def clean():
|
||||
return 42
|
||||
"""
|
||||
)
|
||||
result = _run_audit("--root", str(f.parent), "--strict")
|
||||
assert result.returncode == 0, (
|
||||
f"clean file should pass --strict; got {result.returncode}\nstdout: {result.stdout}"
|
||||
)
|
||||
|
||||
|
||||
def test_local_import_flagged_as_strict(fixture_file):
|
||||
f = fixture_file(
|
||||
"""
|
||||
def bad():
|
||||
from pathlib import Path
|
||||
return Path
|
||||
"""
|
||||
)
|
||||
result = _run_audit("--root", str(f.parent), "--strict")
|
||||
assert result.returncode == 1, "local import should be strict violation"
|
||||
assert "LOCAL_IMPORT" in result.stdout
|
||||
assert "bad()" in result.stdout
|
||||
|
||||
|
||||
def test_prefix_aliasing_flagged_as_strict(fixture_file):
|
||||
f = fixture_file(
|
||||
"""
|
||||
from pathlib import Path as _P
|
||||
def use():
|
||||
return _P
|
||||
"""
|
||||
)
|
||||
result = _run_audit("--root", str(f.parent), "--strict")
|
||||
assert result.returncode == 1, "_PREFIX alias should be strict violation"
|
||||
assert "PREFIX_ALIAS" in result.stdout
|
||||
assert "_P" in result.stdout
|
||||
|
||||
|
||||
def test_optional_import_in_try_except_allowed(fixture_file):
|
||||
"""A `from X import Y` inside `try/except ImportError:` is ALLOWED per §17.9a
|
||||
(canonical "optional dependency" pattern)."""
|
||||
f = fixture_file(
|
||||
"""
|
||||
def lazy_load():
|
||||
try:
|
||||
from optional_dep import thing
|
||||
except ImportError:
|
||||
thing = None
|
||||
return thing
|
||||
"""
|
||||
)
|
||||
result = _run_audit("--root", str(f.parent), "--strict")
|
||||
assert result.returncode == 0, (
|
||||
f"optional-import try/except should pass --strict; got {result.returncode}\nstdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
|
||||
|
||||
def test_repeated_from_dict_is_info_only(fixture_file):
|
||||
"""Repeated .from_dict() in the same expression is reported but NOT strict (§17.9c)."""
|
||||
f = fixture_file(
|
||||
"""
|
||||
from src.type_aliases import Foo
|
||||
def use(d):
|
||||
return Foo.from_dict(d).x + Foo.from_dict(d).y
|
||||
"""
|
||||
)
|
||||
result = _run_audit("--root", str(f.parent))
|
||||
# Info finding, not strict
|
||||
assert "REPEATED_FROM_DICT" in result.stdout
|
||||
result_strict = _run_audit("--root", str(f.parent), "--strict")
|
||||
# REPEATED_FROM_DICT should NOT cause --strict to exit 1
|
||||
assert result_strict.returncode == 0, "REPEATED_FROM_DICT is info-only; should not fail --strict"
|
||||
|
||||
|
||||
def test_script_runs_on_real_src():
|
||||
"""Smoke test: the script runs on the actual src/ tree without erroring."""
|
||||
result = _run_audit("--root", "src")
|
||||
# Return code is 1 if strict violations exist; we only check it RAN
|
||||
assert "STRICT" in result.stdout or "INFO" in result.stdout
|
||||
@@ -1,4 +1,4 @@
|
||||
from src.command_palette import Command, ScoredCommand, fuzzy_match
|
||||
from src.commands import Command, ScoredCommand, fuzzy_match
|
||||
|
||||
|
||||
def _cmd(id: str, title: str) -> Command:
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
"""Tests that src/commands.py has NO top-level src.command_palette import.
|
||||
"""Tests for the post-Phase 1.3 architecture.
|
||||
|
||||
Per spec.md:2.2 Layer 1, the main thread's import chain must not include
|
||||
heavy feature-gated modules. src.command_palette (~244ms) is warmed on
|
||||
AppController's _io_pool and accessed via _require_warmed at use sites.
|
||||
Per module_taxonomy_refactor_20260627 Phase 1.3, src/command_palette.py was
|
||||
deleted and its content split by responsibility:
|
||||
- Command / ScoredCommand / CommandRegistry / fuzzy_match (data/ops) -> src/commands.py
|
||||
- render_palette_modal (view) -> src/gui_2.py
|
||||
|
||||
src/commands.py is a particularly tricky case: it has 32 `@registry.register`
|
||||
decorators on its command functions. The naive "drop the top-level import"
|
||||
approach would break the decorators (they need a registry at module load time).
|
||||
src/commands.py is a thin data module and can be imported eagerly (no
|
||||
require_warmed lazy load is needed; the original lazy-load pattern in
|
||||
startup_speedup_20260606 was specifically to defer the heavy src/command_palette
|
||||
which pulled in imgui at module load).
|
||||
|
||||
Solution: a lazy registry proxy. The @registry.register decorator becomes a
|
||||
no-op that queues the function; the real CommandRegistry is created on first
|
||||
attribute access to the proxy (e.g. registry.all, registry.get). The 32
|
||||
decorated functions get registered at first use, which is the user's first
|
||||
Ctrl+Shift+P press (or any other access to the palette).
|
||||
|
||||
These tests run in a fresh subprocess to ensure no warmup state leaks
|
||||
from the test runner. We assert:
|
||||
- `src.command_palette` is NOT in `sys.modules` after `import src.commands`
|
||||
- The lazy registry proxy works: `from src.commands import registry` succeeds
|
||||
- Accessing `registry.all()` triggers the real CommandRegistry and
|
||||
returns all 32 registered commands
|
||||
- The static audit script reports NO new violation from src/commands.py
|
||||
These tests run in fresh subprocesses to ensure no warmup state leaks from
|
||||
the test runner. We assert:
|
||||
- src/commands imports cleanly and exposes Command + CommandRegistry
|
||||
- src/gui_2 exposes render_palette_modal
|
||||
- src/commands does NOT import gui_2 at module level (avoids circular)
|
||||
- The static audit detects no top-level command_palette import (since the
|
||||
module no longer exists)
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
@@ -42,77 +38,63 @@ def _run_in_subprocess(snippet: str) -> subprocess.CompletedProcess:
|
||||
)
|
||||
|
||||
|
||||
def test_commands_does_not_import_command_palette_at_module_level() -> None:
|
||||
def test_commands_exposes_command_and_registry() -> None:
|
||||
res = _run_in_subprocess("""
|
||||
import sys
|
||||
import src.commands
|
||||
print('src.command_palette' in sys.modules)
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
assert res.stdout.strip() == "False", f"src.commands triggered src.command_palette import: {res.stdout}"
|
||||
|
||||
|
||||
def test_commands_lazy_registry_proxies_to_real_registry() -> None:
|
||||
"""Accessing registry.all() should trigger real init and return registered commands."""
|
||||
res = _run_in_subprocess("""
|
||||
from src.commands import registry
|
||||
# Access .all() triggers real CommandRegistry creation
|
||||
all_cmds = registry.all()
|
||||
print(len(list(all_cmds)))
|
||||
# After access, src.command_palette SHOULD be in sys.modules
|
||||
import sys
|
||||
print('src.command_palette' in sys.modules)
|
||||
from src.commands import Command, CommandRegistry, fuzzy_match, ScoredCommand
|
||||
r = CommandRegistry()
|
||||
def my_cmd(app): pass
|
||||
r.register(my_cmd)
|
||||
print(len(r.all()))
|
||||
print(r.all()[0].id)
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
lines = res.stdout.strip().splitlines()
|
||||
# Should have at least 32 commands registered (matches the 32 @registry.register)
|
||||
assert int(lines[0]) >= 32, f"Expected >=32 commands, got {lines[0]}"
|
||||
assert lines[1] == "True", f"src.command_palette should be loaded after registry access, got {lines[1]}"
|
||||
assert lines[0] == "1"
|
||||
assert lines[1] == "my_cmd"
|
||||
|
||||
|
||||
def test_commands_register_decorator_is_lazy() -> None:
|
||||
"""The @registry.register decorator should NOT trigger command_palette import at module load."""
|
||||
def test_gui_2_exposes_render_palette_modal() -> None:
|
||||
res = _run_in_subprocess("""
|
||||
# Fresh subprocess, just import commands
|
||||
import sys
|
||||
import src.commands
|
||||
# Verify decorator ran but did not trigger command_palette
|
||||
# (the lazy proxy just queues; real init is deferred)
|
||||
print('src.command_palette' in sys.modules)
|
||||
# Verify the function references still exist
|
||||
from src.commands import toggle_command_palette
|
||||
print(callable(toggle_command_palette))
|
||||
from src.gui_2 import render_palette_modal
|
||||
print(callable(render_palette_modal))
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
lines = res.stdout.strip().splitlines()
|
||||
assert lines[0] == "False", f"Decorator should not trigger command_palette, got {lines[0]}"
|
||||
assert lines[1] == "True", f"toggle_command_palette should be a callable, got {lines[1]}"
|
||||
assert res.stdout.strip() == "True"
|
||||
|
||||
|
||||
def test_audit_main_thread_imports_sees_no_new_violation_from_commands() -> None:
|
||||
"""Run the static audit and check that src/commands.py contributes no
|
||||
new command_palette violations.
|
||||
def test_commands_does_not_import_gui_2_at_module_level() -> None:
|
||||
"""src/commands is imported by src/commands registration sites and by
|
||||
gui_2.render_palette_modal. To avoid a circular import, commands.py must
|
||||
NOT import gui_2 at the top of the file. (TYPE_CHECKING imports are
|
||||
allowed because they don't execute at runtime.)
|
||||
"""
|
||||
res = _run_in_subprocess("""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
root = Path('.').resolve()
|
||||
commands_path = root / 'src' / 'commands.py'
|
||||
tree = ast.parse(commands_path.read_text(encoding='utf-8'))
|
||||
heavy = ['src.command_palette', 'command_palette']
|
||||
commands_path = Path('src') / 'commands.py'
|
||||
source = commands_path.read_text(encoding='utf-8')
|
||||
tree = ast.parse(source)
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
for h in heavy:
|
||||
if alias.name == h or alias.name.startswith(h + '.'):
|
||||
print('VIOLATION:', alias.name)
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
if node.module:
|
||||
for h in heavy:
|
||||
if node.module == h or node.module.startswith(h + '.'):
|
||||
print('VIOLATION:', node.module)
|
||||
if isinstance(node, (ast.Import, ast.ImportFrom)):
|
||||
mod = getattr(node, 'module', None) or (node.names[0].name if node.names else '')
|
||||
if mod and ('gui_2' in mod or mod.endswith('gui_2')):
|
||||
print('VIOLATION:', mod)
|
||||
print('OK')
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
assert "OK" in res.stdout
|
||||
assert "VIOLATION" not in res.stdout
|
||||
assert "OK" in res.stdout
|
||||
|
||||
|
||||
def test_command_palette_module_no_longer_exists() -> None:
|
||||
"""src/command_palette.py was deleted in Phase 1.3; this is a regression guard."""
|
||||
res = _run_in_subprocess("""
|
||||
import importlib
|
||||
try:
|
||||
importlib.import_module('src.command_palette')
|
||||
print('EXISTS')
|
||||
except ModuleNotFoundError:
|
||||
print('NOT_FOUND')
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
assert res.stdout.strip() == "NOT_FOUND"
|
||||
@@ -2,8 +2,8 @@ import pytest
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
from src.diff_viewer import (
|
||||
parse_diff, DiffFile, DiffHunk, parse_hunk_header,
|
||||
from src.gui_2 import (
|
||||
parse_diff, DiffFile, DiffHunk, parse_hunk_header,
|
||||
get_line_color, apply_patch_to_file
|
||||
)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ def test_send_grok_uses_xai_endpoint(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert mock_client.chat.completions.create.called
|
||||
|
||||
def test_grok_2_vision_supports_image() -> None:
|
||||
from src.vendor_capabilities import get_capabilities
|
||||
from src.ai_client import get_capabilities
|
||||
caps = get_capabilities("grok", "grok-2-vision")
|
||||
assert caps.vision is True
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_llama_model_discovery_unions_ollama_and_openrouter() -> None:
|
||||
assert "llama-3.3-70b-specdec" in models
|
||||
|
||||
def test_llama_3_2_vision_vision_capability() -> None:
|
||||
from src.vendor_capabilities import get_capabilities
|
||||
from src.ai_client import get_capabilities
|
||||
caps = get_capabilities("llama", "llama-3.2-11b-vision-preview")
|
||||
assert caps.vision is True
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ Phase 1 of any_type_componentization_20260621. Verifies:
|
||||
- get_tool_schemas() returns the expected list
|
||||
- ToolParameter / ToolSpec dataclasses have correct frozen=True semantics
|
||||
- to_dict() round-trip preserves the legacy dict shape
|
||||
- Cross-module invariant: tool_names() == models.AGENT_TOOL_NAMES subset
|
||||
|
||||
CONVENTION: 1-space indentation. NO COMMENTS.
|
||||
"""
|
||||
@@ -15,7 +14,6 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from src import mcp_tool_specs
|
||||
from src import models
|
||||
|
||||
|
||||
EXPECTED_TOOLS: set[str] = {
|
||||
@@ -107,14 +105,6 @@ def test_tool_parameter_to_dict_includes_enum() -> None:
|
||||
assert 'before' in d['enum']
|
||||
|
||||
|
||||
def test_tool_names_subset_of_models_agent_tool_names() -> None:
|
||||
"""Cross-module invariant: every MCP tool is also an agent tool."""
|
||||
native_names = mcp_tool_specs.tool_names()
|
||||
agent_names = set(models.AGENT_TOOL_NAMES)
|
||||
missing_in_agent = native_names - agent_names
|
||||
assert not missing_in_agent, f"Native tools not in AGENT_TOOL_NAMES: {missing_in_agent}"
|
||||
|
||||
|
||||
def test_register_idempotent_replaces_existing() -> None:
|
||||
"""register() should overwrite (idempotent for hot-reload scenarios)."""
|
||||
from src.mcp_tool_specs import ToolSpec, ToolParameter, register
|
||||
|
||||
@@ -42,7 +42,7 @@ def test_minimax_reasoning_extractor_used_when_caps_reasoning_true() -> None:
|
||||
def _fake_send(client, request, *, capabilities):
|
||||
captured_kwargs.append({"model": request.model})
|
||||
return MagicMock(text="ok", tool_calls=[], usage=UsageStats(input_tokens=0, output_tokens=0, cache_read_tokens=0, cache_creation_tokens=0), raw_response=None)
|
||||
from src.vendor_capabilities import register, VendorCapabilities
|
||||
from src.ai_client import register, VendorCapabilities
|
||||
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.5', reasoning=True))
|
||||
with patch.object(oc, "send_openai_compatible", side_effect=_fake_send), \
|
||||
patch("src.ai_client._ensure_minimax_client", return_value=MagicMock()), \
|
||||
@@ -54,7 +54,7 @@ def test_minimax_reasoning_extractor_omitted_when_caps_reasoning_false() -> None
|
||||
"""caps.reasoning=False (M2/M2.1) should NOT pass the reasoning_extractor (avoid useless getattr)."""
|
||||
from src import openai_compatible as oc
|
||||
from src.openai_schemas import UsageStats
|
||||
from src.vendor_capabilities import register, VendorCapabilities
|
||||
from src.ai_client import register, VendorCapabilities
|
||||
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2', reasoning=False))
|
||||
captured_kwargs: list[dict] = []
|
||||
def _fake_send(client, request, *, capabilities):
|
||||
|
||||
@@ -46,7 +46,7 @@ def test_models_can_still_call_save_config_after_lazy_load() -> None:
|
||||
"theme": {"palette": "solarized_dark", "font_size": 16.0},
|
||||
}
|
||||
try:
|
||||
src.models._save_config_to_disk(config)
|
||||
src.models.save_config_to_disk(config)
|
||||
except Exception as e:
|
||||
pytest.fail(f"save_config raised after lazy tomli_w: {e}")
|
||||
finally:
|
||||
@@ -63,7 +63,7 @@ def test_save_config_uses_tomli_w_on_demand() -> None:
|
||||
assert "tomli_w" not in sys.modules
|
||||
# Call save_config - this should trigger the import
|
||||
try:
|
||||
src.models._save_config_to_disk({"test_key": "test_value"})
|
||||
src.models.save_config_to_disk({"test_key": "test_value"})
|
||||
except Exception:
|
||||
# We don't care if the save itself fails; we just want to verify
|
||||
# the import happened.
|
||||
|
||||
@@ -6,7 +6,7 @@ from src.openai_compatible import (
|
||||
send_openai_compatible,
|
||||
)
|
||||
from src.openai_schemas import ChatMessage
|
||||
from src.vendor_capabilities import VendorCapabilities, register
|
||||
from src.ai_client import VendorCapabilities, register
|
||||
|
||||
@pytest.fixture
|
||||
def caps() -> VendorCapabilities:
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import pytest
|
||||
from src.vendor_capabilities import VendorCapabilities, get_capabilities, register
|
||||
from src.ai_client import VendorCapabilities, get_capabilities, register
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_registry():
|
||||
import src.vendor_capabilities
|
||||
snapshot = src.vendor_capabilities._REGISTRY.copy()
|
||||
import src.ai_client as _ai
|
||||
snapshot = _ai._VENDOR_REGISTRY.copy()
|
||||
yield
|
||||
src.vendor_capabilities._REGISTRY.clear()
|
||||
src.vendor_capabilities._REGISTRY.update(snapshot)
|
||||
_ai._VENDOR_REGISTRY.clear()
|
||||
_ai._VENDOR_REGISTRY.update(snapshot)
|
||||
|
||||
def test_registry_lookup_known_model():
|
||||
caps = VendorCapabilities(
|
||||
@@ -217,6 +217,6 @@ def test_v2_capability_badge_helper_skips_disabled_fields() -> None:
|
||||
a live context, but we can verify the helper is a no-op on
|
||||
the no-cap case.)"""
|
||||
from src.gui_2 import _render_v2_capability_badges
|
||||
from src.vendor_capabilities import VendorCapabilities
|
||||
from src.ai_client import VendorCapabilities
|
||||
empty_caps = VendorCapabilities(vendor='test', model='empty')
|
||||
_render_v2_capability_badges(empty_caps)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from src.vendor_state import get_vendor_state, VendorMetric
|
||||
from src.gui_2 import _get_vendor_state_metrics as get_vendor_state
|
||||
from src.ai_client import VendorMetric
|
||||
|
||||
class _StubTT:
|
||||
def __init__(self, used=0, limit=0, cache_hits=0, cache_misses=0):
|
||||
|
||||
Reference in New Issue
Block a user