Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-authored-by: Lucas Marandat <31749711+lucasmrdt@users.noreply.github.com>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai>
Co-authored-by: Peter Evers <pevers90@gmail.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Pierre Rossinès <pierre.rossines@protonmail.com>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-04-09 18:40:46 +02:00 committed by GitHub
parent 90763daf81
commit e9a9217cc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
113 changed files with 7202 additions and 541 deletions

View file

@ -78,3 +78,29 @@ class TestAgentManager:
assert agent.name == "default"
assert agent.agent_type == AgentType.AGENT
def test_initial_agent_rejects_subagent(self) -> None:
"""Test that creating AgentManager with a subagent as initial_agent raises."""
config = build_test_vibe_config(
include_project_context=False, include_prompt_detail=False
)
with pytest.raises(ValueError, match="cannot be used as the primary agent"):
AgentManager(lambda: config, initial_agent="explore")
def test_initial_agent_accepts_subagent_when_allowed(self) -> None:
"""Test that allow_subagent=True permits subagent as initial_agent."""
config = build_test_vibe_config(
include_project_context=False, include_prompt_detail=False
)
manager = AgentManager(
lambda: config, initial_agent="explore", allow_subagent=True
)
assert manager.active_profile.name == "explore"
def test_initial_agent_accepts_agent_type(self) -> None:
"""Test that creating AgentManager with an agent-type agent works."""
config = build_test_vibe_config(
include_project_context=False, include_prompt_detail=False
)
manager = AgentManager(lambda: config, initial_agent="plan")
assert manager.active_profile.name == "plan"

View file

@ -2,11 +2,11 @@ from __future__ import annotations
import pytest
from vibe.core.config import OtelExporterConfig, ProviderConfig, VibeConfig
from vibe.core.config import OtelSpanExporterConfig, ProviderConfig, VibeConfig
from vibe.core.types import Backend
class TestOtelExporterConfig:
class TestOtelSpanExporterConfig:
def test_derives_endpoint_from_mistral_provider(
self, vibe_config: VibeConfig, monkeypatch: pytest.MonkeyPatch
) -> None:
@ -22,7 +22,7 @@ class TestOtelExporterConfig:
]
}
)
result = config.otel_exporter_config
result = config.otel_span_exporter_config
assert result is not None
assert result.endpoint == "https://customer.mistral.ai/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-test"}
@ -49,7 +49,7 @@ class TestOtelExporterConfig:
]
}
)
result = config.otel_exporter_config
result = config.otel_span_exporter_config
assert result is not None
assert result.endpoint == "https://eu.mistral.ai/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-eu"}
@ -67,7 +67,7 @@ class TestOtelExporterConfig:
]
}
)
result = config.otel_exporter_config
result = config.otel_span_exporter_config
assert result is not None
assert result.endpoint == "https://api.mistral.ai/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-fallback"}
@ -76,7 +76,7 @@ class TestOtelExporterConfig:
self, vibe_config: VibeConfig, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("MISTRAL_API_KEY", "sk-default")
result = vibe_config.otel_exporter_config
result = vibe_config.otel_span_exporter_config
assert result is not None
assert result.endpoint == "https://api.mistral.ai/telemetry/v1/traces"
@ -88,7 +88,7 @@ class TestOtelExporterConfig:
) -> None:
monkeypatch.delenv("MISTRAL_API_KEY", raising=False)
with caplog.at_level("WARNING"):
assert vibe_config.otel_exporter_config is None
assert vibe_config.otel_span_exporter_config is None
assert "OTEL tracing enabled but MISTRAL_API_KEY is not set" in caplog.text
def test_custom_api_key_env_var(
@ -108,20 +108,33 @@ class TestOtelExporterConfig:
]
}
)
result = config.otel_exporter_config
result = config.otel_span_exporter_config
assert result is not None
assert result.endpoint == "https://onprem.corp.com/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-custom"}
def test_explicit_otel_endpoint_bypasses_provider_derivation(
def test_explicit_otel_endpoint_appends_default_traces_path(
self, vibe_config: VibeConfig
) -> None:
config = vibe_config.model_copy(
update={"otel_endpoint": "https://my-collector:4318/v1/traces"}
update={"otel_endpoint": "https://my-collector:4318"}
)
result = config.otel_exporter_config
result = config.otel_span_exporter_config
assert result is not None
assert result == OtelExporterConfig(
assert result == OtelSpanExporterConfig(
endpoint="https://my-collector:4318/v1/traces"
)
assert result.headers is None
def test_explicit_otel_endpoint_preserves_path_prefix(
self, vibe_config: VibeConfig
) -> None:
config = vibe_config.model_copy(
update={"otel_endpoint": "https://my-collector:4318/api/public/otel"}
)
result = config.otel_span_exporter_config
assert result is not None
assert result == OtelSpanExporterConfig(
endpoint="https://my-collector:4318/api/public/otel/v1/traces"
)
assert result.headers is None

View file

@ -8,7 +8,12 @@ from unittest.mock import MagicMock, patch
import pytest
from vibe.core.logger import StructuredLogFormatter, apply_logging_config
from vibe.core.logger import (
StructuredLogFormatter,
apply_logging_config,
decode_log_message,
encode_log_message,
)
@pytest.fixture
@ -278,3 +283,63 @@ class TestApplyLoggingConfig:
handler = test_logger.handlers[-1]
assert isinstance(handler, RotatingFileHandler)
assert handler.maxBytes == 5242880
class TestDecodeLogMessage:
def test_plain_message_unchanged(self) -> None:
assert decode_log_message("Hello world") == "Hello world"
def test_decodes_escaped_newline(self) -> None:
assert decode_log_message("hello\\nworld") == "hello\nworld"
def test_decodes_escaped_backslash(self) -> None:
assert decode_log_message("C:\\\\path") == "C:\\path"
def test_decodes_escaped_backslash_before_n(self) -> None:
# This is the bug case: C:\new encoded as C:\\new must decode back to C:\new
assert decode_log_message("C:\\\\new") == "C:\\new"
def test_roundtrip_with_newlines(self) -> None:
original = "line one\nline two\nline three"
encoded = encode_log_message(original)
assert decode_log_message(encoded) == original
def test_roundtrip_with_backslashes(self) -> None:
original = "C:\\Users\\test\\file.txt"
encoded = encode_log_message(original)
assert decode_log_message(encoded) == original
def test_roundtrip_with_backslash_n(self) -> None:
original = "C:\\new folder\\notes.txt"
encoded = encode_log_message(original)
assert decode_log_message(encoded) == original
def test_roundtrip_mixed(self) -> None:
original = "path: C:\\new\nand a newline"
encoded = encode_log_message(original)
assert decode_log_message(encoded) == original
def test_exception_encoding_escapes_backslashes(self) -> None:
formatter = StructuredLogFormatter()
try:
raise ValueError("error in C:\\new\\path")
except ValueError:
import sys
exc_info = sys.exc_info()
record = logging.LogRecord(
name="test",
level=logging.ERROR,
pathname="test.py",
lineno=1,
msg="fail",
args=(),
exc_info=exc_info,
)
output = formatter.format(record)
assert "\n" not in output
# The backslashes in the exception should be escaped
assert "C:\\\\new\\\\path" in output

View file

@ -0,0 +1,430 @@
from __future__ import annotations
from collections.abc import Callable, Generator
from dataclasses import FrozenInstanceError
from datetime import datetime
from pathlib import Path
import threading
import time
import pytest
from vibe.core.log_reader import LogEntry, LogReader
def _wait_for(condition: Callable[[], bool], timeout: float = 3.0) -> bool:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if condition():
return True
time.sleep(0.05)
return False
class TestLogEntry:
def test_log_entry_is_frozen(self) -> None:
entry = LogEntry(
timestamp=datetime.now(),
ppid=1,
pid=123,
level="INFO",
message="test",
raw_line="raw",
line_number=1,
)
with pytest.raises(FrozenInstanceError):
entry.message = "modified" # type: ignore[misc]
class TestLogReaderParsing:
@pytest.fixture
def log_file(self, tmp_path: Path) -> Path:
return tmp_path / "test.log"
def test_parses_valid_info_log(self, log_file: Path) -> None:
log_file.write_text(
"2026-02-08T10:30:45.123000+00:00 1234 5678 INFO Test message\n"
)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert len(result.entries) == 1
assert result.entries[0].level == "INFO"
assert result.entries[0].message == "Test message"
def test_parses_valid_error_log(self, log_file: Path) -> None:
log_file.write_text(
"2026-02-08T10:30:45.123000+00:00 1234 5678 ERROR Error message\n"
)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert result.entries[0].level == "ERROR"
def test_parses_log_with_ppid_pid(self, log_file: Path) -> None:
log_file.write_text("2026-02-08T10:30:45.123000+00:00 1111 2222 DEBUG msg\n")
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert result.entries[0].ppid == 1111
assert result.entries[0].pid == 2222
def test_skips_invalid_log_lines(self, log_file: Path) -> None:
log_file.write_text(
"invalid line\n2026-02-08T10:30:45.123000+00:00 1 2 INFO valid\n"
)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert len(result.entries) == 1
assert result.entries[0].message == "valid"
def test_skips_multiline_continuations(self, log_file: Path) -> None:
log_file.write_text(
"2026-02-08T10:30:45.123000+00:00 1 2 INFO msg\n continuation\n"
)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert len(result.entries) == 1
def test_handles_empty_file(self, log_file: Path) -> None:
log_file.write_text("")
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert result.entries == []
def test_handles_missing_file(self, tmp_path: Path) -> None:
missing = tmp_path / "nonexistent.log"
reader = LogReader(log_file=missing)
result = reader.get_logs()
assert result.entries == []
def test_skips_line_with_invalid_timestamp(self, log_file: Path) -> None:
log_file.write_text("not-a-timestamp 1 2 INFO message\n")
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert result.entries == []
def test_skips_line_with_invalid_level(self, log_file: Path) -> None:
log_file.write_text("2026-02-08T10:30:45.123000+00:00 1 2 UNKNOWN message\n")
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert result.entries == []
def test_skips_line_missing_pid(self, log_file: Path) -> None:
log_file.write_text("2026-02-08T10:30:45.123000+00:00 1 INFO message\n")
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert result.entries == []
def test_skips_empty_lines(self, log_file: Path) -> None:
log_file.write_text("\n\n2026-02-08T10:30:45.123000+00:00 1 2 INFO valid\n\n")
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert len(result.entries) == 1
def test_handles_extremely_long_lines(self, log_file: Path) -> None:
long_message = "x" * (128 * 1024) # 128KB message
log_file.write_text(
f"2026-02-08T10:30:45.123000+00:00 1 2 INFO {long_message}\n"
f"2026-02-08T10:30:46.123000+00:00 1 2 INFO short\n"
)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert len(result.entries) == 2
assert result.entries[0].message == "short"
assert result.entries[1].message == long_message
class TestLogReaderMassiveLogs:
@pytest.fixture
def log_file(self, tmp_path: Path) -> Path:
return tmp_path / "test.log"
def test_limit_prevents_reading_entire_large_file(self, log_file: Path) -> None:
num_lines = 10_000
lines = [
f"2026-02-08T10:30:{i % 60:02d}.{i:06d}+00:00 1 2 INFO Message {i}\n"
for i in range(num_lines)
]
log_file.write_text("".join(lines))
reader = LogReader(log_file=log_file)
result = reader.get_logs(limit=10)
assert len(result.entries) == 10
assert result.has_more is True
def test_handles_file_with_mostly_invalid_lines(self, log_file: Path) -> None:
invalid_lines = ["garbage line\n"] * 1000
valid_line = "2026-02-08T10:30:45.123000+00:00 1 2 INFO valid entry\n"
log_file.write_text("".join(invalid_lines) + valid_line)
reader = LogReader(log_file=log_file)
result = reader.get_logs(limit=100)
assert len(result.entries) == 1
assert result.entries[0].message == "valid entry"
def test_handles_binary_garbage_in_file(self, log_file: Path) -> None:
binary_garbage = bytes(range(256))
valid_line = b"2026-02-08T10:30:45.123000+00:00 1 2 INFO valid after binary\n"
log_file.write_bytes(binary_garbage + b"\n" + valid_line)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert len(result.entries) == 1
assert result.entries[0].message == "valid after binary"
def test_handles_null_bytes_in_lines(self, log_file: Path) -> None:
line_with_nulls = (
"2026-02-08T10:30:45.123000+00:00 1 2 INFO msg\x00with\x00nulls\n"
)
valid_line = "2026-02-08T10:30:46.123000+00:00 1 2 INFO clean message\n"
log_file.write_text(line_with_nulls + valid_line)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert len(result.entries) == 2
assert result.entries[0].message == "clean message"
assert result.entries[1].message == "msg\x00with\x00nulls"
def test_handles_massive_single_line_without_newline(self, log_file: Path) -> None:
massive_line = "x" * (1024 * 1024)
log_file.write_text(massive_line)
reader = LogReader(log_file=log_file)
result = reader.get_logs()
assert result.entries == []
class TestLogReaderPagination:
@pytest.fixture
def log_file_with_entries(self, tmp_path: Path) -> Path:
log_file = tmp_path / "test.log"
lines = [
f"2026-02-08T10:30:{i:02d}.000000+00:00 1 2 INFO Message {i}\n"
for i in range(10)
]
log_file.write_text("".join(lines))
return log_file
def test_returns_logs_newest_first(self, log_file_with_entries: Path) -> None:
reader = LogReader(log_file=log_file_with_entries)
result = reader.get_logs()
assert "Message 9" in result.entries[0].message
def test_limit_restricts_results(self, log_file_with_entries: Path) -> None:
reader = LogReader(log_file=log_file_with_entries)
result = reader.get_logs(limit=3)
assert len(result.entries) == 3
def test_has_more_false_when_exhausted(self, log_file_with_entries: Path) -> None:
reader = LogReader(log_file=log_file_with_entries)
result = reader.get_logs(limit=100)
assert result.has_more is False
def test_cursor_continues_from_previous_position(
self, log_file_with_entries: Path
) -> None:
reader = LogReader(log_file=log_file_with_entries)
page1 = reader.get_logs(limit=3)
assert len(page1.entries) == 3
assert page1.has_more is True
assert page1.cursor is not None
page2 = reader.get_logs(limit=3, offset=page1.cursor)
assert len(page2.entries) == 3
assert page2.has_more is True
assert page2.entries[0].message != page1.entries[-1].message
class TestLogReaderWatcher:
@pytest.fixture
def log_reader(self, tmp_path: Path) -> Generator[LogReader, None, None]:
log_file = tmp_path / "test.log"
log_file.write_text("")
reader = LogReader(log_file=log_file)
yield reader
reader.shutdown()
def test_start_watching_sets_is_watching(self, log_reader: LogReader) -> None:
log_reader.start_watching()
assert log_reader.is_watching is True
def test_stop_watching_clears_is_watching(self, log_reader: LogReader) -> None:
log_reader.start_watching()
log_reader.stop_watching()
assert log_reader.is_watching is False
def test_consumer_receives_new_entries(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
log_file.write_text("")
received: list[LogEntry] = []
reader = LogReader(log_file=log_file, consumer=received.append)
try:
reader.start_watching()
with log_file.open("a") as f:
f.write("2026-02-08T10:30:45.123000+00:00 1 2 INFO New entry\n")
assert _wait_for(lambda: len(received) >= 1)
assert received[0].message == "New entry"
finally:
reader.shutdown()
def test_toggle_watching_on_off(self, log_reader: LogReader) -> None:
log_reader.start_watching()
assert log_reader.is_watching is True
log_reader.stop_watching()
assert log_reader.is_watching is False
log_reader.start_watching()
assert log_reader.is_watching is True
def test_set_consumer_updates_callback(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
log_file.write_text("")
received: list[LogEntry] = []
reader = LogReader(log_file=log_file)
try:
reader.set_consumer(received.append)
reader.start_watching()
with log_file.open("a") as f:
f.write("2026-02-08T10:30:45.123000+00:00 1 2 INFO Entry\n")
assert _wait_for(lambda: len(received) >= 1)
finally:
reader.shutdown()
def test_consumer_can_call_get_logs_without_deadlock(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
log_file.write_text("")
reader = LogReader(log_file=log_file, poll_interval=0.05)
callback_completed = threading.Event()
callback_errors: list[Exception] = []
def consumer(_: LogEntry) -> None:
try:
reader.get_logs(limit=1)
except Exception as exc:
callback_errors.append(exc)
finally:
callback_completed.set()
reader.set_consumer(consumer)
try:
reader.start_watching()
with log_file.open("a") as f:
f.write("2026-02-08T10:30:45.123000+00:00 1 2 INFO Entry\n")
assert callback_completed.wait(timeout=1.0)
assert callback_errors == []
finally:
reader.shutdown()
class TestLogReaderCleanup:
def test_shutdown_stops_watching(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
log_file.write_text("")
reader = LogReader(log_file=log_file)
reader.start_watching()
assert reader.is_watching is True
reader.shutdown()
assert reader.is_watching is False
class TestLogReaderLineNumbers:
def test_line_numbers_relative_from_end(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
lines = [
f"2026-02-08T10:30:{i:02d}.000000+00:00 1 2 INFO Message {i}\n"
for i in range(5)
]
log_file.write_text("".join(lines))
reader = LogReader(log_file=log_file)
result = reader.get_logs(limit=5)
assert result.entries[0].line_number == 1 # Newest
assert result.entries[0].message == "Message 4"
assert result.entries[1].line_number == 2 # Second newest
assert result.entries[1].message == "Message 3"
def test_live_entries_have_zero_line_number(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
log_file.write_text("")
received: list[LogEntry] = []
reader = LogReader(log_file=log_file, consumer=received.append)
try:
reader.start_watching()
with log_file.open("a") as f:
f.write("2026-02-08T10:30:45.123000+00:00 1 2 INFO Live entry\n")
assert _wait_for(lambda: len(received) >= 1)
assert received[0].line_number == 0
assert received[0].message == "Live entry"
finally:
reader.shutdown()
class TestLogReaderCursorDrift:
def test_cursor_stable_with_new_logs(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
lines = [
f"2026-02-08T10:30:{i:02d}.000000+00:00 1 2 INFO Message {i}\n"
for i in range(10)
]
log_file.write_text("".join(lines))
received: list[LogEntry] = []
reader = LogReader(log_file=log_file, consumer=received.append)
try:
page1 = reader.get_logs(limit=3)
assert page1.entries[0].message == "Message 9"
assert page1.cursor == 3
reader.start_watching()
with log_file.open("a") as f:
f.write("2026-02-08T10:30:50.000000+00:00 1 2 INFO New message 1\n")
f.write("2026-02-08T10:30:51.000000+00:00 1 2 INFO New message 2\n")
assert _wait_for(lambda: len(received) >= 2)
page2 = reader.get_logs(limit=3, offset=page1.cursor)
assert page2.entries[0].message == "Message 6"
assert page2.entries[1].message == "Message 5"
assert page2.entries[2].message == "Message 4"
finally:
reader.shutdown()
def test_stop_watching_resets_counter(self, tmp_path: Path) -> None:
log_file = tmp_path / "test.log"
lines = [
f"2026-02-08T10:30:{i:02d}.000000+00:00 1 2 INFO Message {i}\n"
for i in range(5)
]
log_file.write_text("".join(lines))
received: list[LogEntry] = []
reader = LogReader(log_file=log_file, consumer=received.append)
try:
reader.start_watching()
with log_file.open("a") as f:
f.write("2026-02-08T10:30:50.000000+00:00 1 2 INFO New message\n")
assert _wait_for(lambda: len(received) >= 1)
reader.stop_watching()
result = reader.get_logs(limit=10)
assert len(result.entries) == 6
assert result.entries[0].message == "New message"
finally:
reader.shutdown()

View file

@ -225,6 +225,18 @@ class TestNuageClientGetChatAssistantUrl:
url = await nuage.get_chat_assistant_url("exec-123")
assert url == "https://chat.example.com/thread/123"
@pytest.mark.asyncio
async def test_get_chat_assistant_url_none(
self, nuage: NuageClient, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.is_success = True
mock_response.json.return_value = {"result": {"chat_url": None}}
mock_client.post = AsyncMock(return_value=mock_response)
url = await nuage.get_chat_assistant_url("exec-123")
assert url is None
@pytest.mark.asyncio
async def test_get_chat_assistant_url_failure(
self, nuage: NuageClient, mock_client: MagicMock

View file

@ -382,6 +382,31 @@ class TestTeleportServiceExecute:
assert isinstance(events[4], TeleportAuthCompleteEvent)
assert isinstance(events[-1], TeleportCompleteEvent)
@pytest.mark.asyncio
async def test_execute_raises_when_chat_url_is_none(
self,
service: TeleportService,
git_info: GitRepoInfo,
mock_github_connected: MagicMock,
) -> None:
service._git.get_info = AsyncMock(return_value=git_info)
service._git.is_commit_pushed = AsyncMock(return_value=True)
mock_nuage = MagicMock()
mock_nuage.start_workflow = AsyncMock(return_value="exec-123")
mock_nuage.get_github_integration = AsyncMock(
return_value=mock_github_connected
)
mock_nuage.get_chat_assistant_url = AsyncMock(return_value=None)
service._nuage = mock_nuage
session = TeleportSession()
gen = service.execute("test prompt", session)
with pytest.raises(ServiceTeleportError, match="not available"):
async for _ in gen:
pass
@pytest.mark.asyncio
async def test_execute_uses_default_prompt_when_none(
self,

View file

@ -0,0 +1,50 @@
from __future__ import annotations
from collections.abc import Generator
from pathlib import Path
import pytest
from vibe.core.autocompletion.file_indexer.watcher import WatchController
class TestWatchControllerIsWatching:
@pytest.fixture
def watch_dir(self, tmp_path: Path) -> Path:
return tmp_path / "watch"
@pytest.fixture
def watcher(self) -> Generator[WatchController, None, None]:
changes: list = []
controller = WatchController(on_changes=lambda root, c: changes.extend(c))
yield controller
controller.stop()
def test_is_watching_false_initially(self, watcher: WatchController) -> None:
assert watcher.is_watching is False
def test_is_watching_true_after_start(
self, watcher: WatchController, watch_dir: Path
) -> None:
watch_dir.mkdir()
watcher.start(watch_dir)
assert watcher.is_watching is True
def test_is_watching_false_after_stop(
self, watcher: WatchController, watch_dir: Path
) -> None:
watch_dir.mkdir()
watcher.start(watch_dir)
assert watcher.is_watching is True
watcher.stop()
assert watcher.is_watching is False
def test_is_watching_true_after_restart(
self, watcher: WatchController, watch_dir: Path
) -> None:
watch_dir.mkdir()
watcher.start(watch_dir)
watcher.stop()
assert watcher.is_watching is False
watcher.start(watch_dir)
assert watcher.is_watching is True

View file

@ -34,7 +34,7 @@ def _setup_manager(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[
def _make_read_file() -> ReadFile:
return ReadFile(config=ReadFileToolConfig(), state=ReadFileState())
return ReadFile(config_getter=lambda: ReadFileToolConfig(), state=ReadFileState())
class TestGetResultExtra: