mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-18 18:16:38 +03:00
refactor: remove verified dead code
This commit is contained in:
@@ -169,19 +169,6 @@ class TestDiffCommits:
|
||||
assert git_ready.diff_commits("deadbeef", "cafebabe") == ""
|
||||
|
||||
|
||||
class TestFindCommit:
|
||||
def test_finds_by_prefix(self, git_ready):
|
||||
ws = git_ready._workspace
|
||||
(ws / "SOUL.md").write_text("v2", encoding="utf-8")
|
||||
sha = git_ready.auto_commit("v2")
|
||||
found = git_ready.find_commit(sha[:4])
|
||||
assert found is not None
|
||||
assert found.sha == sha
|
||||
|
||||
def test_returns_none_for_unknown(self, git_ready):
|
||||
assert git_ready.find_commit("deadbeef") is None
|
||||
|
||||
|
||||
class TestShowCommitDiff:
|
||||
def test_returns_commit_with_diff(self, git_ready):
|
||||
ws = git_ready._workspace
|
||||
|
||||
@@ -9,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import pytest
|
||||
|
||||
from agent.runner_helpers import make_run_spec
|
||||
from nanobot.agent.automation_turns import publish_next_deferred_turn
|
||||
from nanobot.config.schema import AgentDefaults
|
||||
from nanobot.providers.base import LLMResponse, ToolCallRequest
|
||||
|
||||
@@ -1047,7 +1048,11 @@ async def test_cron_turn_deferred_while_session_active(tmp_path):
|
||||
assert loop._cron_turns.deferred_queues[session_key] == [msg]
|
||||
assert loop.pending_cron_job_ids_for_session(session_key) == {"job-1"}
|
||||
|
||||
await loop._cron_turns.publish_next_deferred(session_key)
|
||||
await publish_next_deferred_turn(
|
||||
deferred_queues=loop._cron_turns.deferred_queues,
|
||||
publish_inbound=loop.bus.publish_inbound,
|
||||
session_key=session_key,
|
||||
)
|
||||
queued = await asyncio.wait_for(loop.bus.consume_inbound(), timeout=0.5)
|
||||
assert queued is msg
|
||||
assert session_key not in loop._cron_turns.deferred_queues
|
||||
@@ -1097,7 +1102,11 @@ async def test_local_trigger_turn_deferred_while_session_active(tmp_path):
|
||||
assert loop._local_trigger_turns.deferred_queues[session_key] == [msg]
|
||||
assert loop.pending_local_trigger_ids_for_session(session_key) == {"trg_123"}
|
||||
|
||||
assert await loop._local_trigger_turns.publish_next_deferred(session_key) is True
|
||||
assert await publish_next_deferred_turn(
|
||||
deferred_queues=loop._local_trigger_turns.deferred_queues,
|
||||
publish_inbound=loop.bus.publish_inbound,
|
||||
session_key=session_key,
|
||||
) is True
|
||||
queued = await asyncio.wait_for(loop.bus.consume_inbound(), timeout=0.5)
|
||||
assert queued is msg
|
||||
assert session_key not in loop._local_trigger_turns.deferred_queues
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
"""Tests for CLI Apps loop helpers."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from nanobot.apps.cli.service import CliAppManager
|
||||
from nanobot.apps.cli.utils import runtime_lines, session_extra
|
||||
from nanobot.apps.cli.utils import runtime_lines_for_request, session_extra
|
||||
|
||||
|
||||
def test_session_extra_returns_cli_apps_only_when_present() -> None:
|
||||
@@ -30,8 +28,9 @@ def test_cli_app_mentions_inject_runtime_metadata(tmp_path, monkeypatch):
|
||||
}
|
||||
)
|
||||
|
||||
lines = runtime_lines(
|
||||
SimpleNamespace(content="please use @zoom tonight; ignore @krita?", metadata={}),
|
||||
lines = runtime_lines_for_request(
|
||||
"please use @zoom tonight; ignore @krita?",
|
||||
{},
|
||||
tmp_path,
|
||||
)
|
||||
|
||||
@@ -43,17 +42,15 @@ def test_cli_app_mentions_inject_runtime_metadata(tmp_path, monkeypatch):
|
||||
|
||||
|
||||
def test_structured_cli_app_attachment_injects_runtime_metadata(tmp_path):
|
||||
lines = runtime_lines(
|
||||
SimpleNamespace(
|
||||
content="please use @zoom tonight",
|
||||
metadata={
|
||||
"cli_apps": [{
|
||||
"name": "zoom",
|
||||
"entry_point": "cli-anything-zoom",
|
||||
"display_name": "Zoom",
|
||||
}],
|
||||
},
|
||||
),
|
||||
lines = runtime_lines_for_request(
|
||||
"please use @zoom tonight",
|
||||
{
|
||||
"cli_apps": [{
|
||||
"name": "zoom",
|
||||
"entry_point": "cli-anything-zoom",
|
||||
"display_name": "Zoom",
|
||||
}],
|
||||
},
|
||||
tmp_path,
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from zipfile import ZipFile
|
||||
import pytest
|
||||
|
||||
from nanobot.utils.document import (
|
||||
SUPPORTED_EXTENSIONS,
|
||||
PdfSafetyError,
|
||||
_is_text_extension,
|
||||
extract_pdf_pages,
|
||||
@@ -14,31 +13,6 @@ from nanobot.utils.document import (
|
||||
)
|
||||
|
||||
|
||||
class TestSupportedExtensions:
|
||||
"""Test the SUPPORTED_EXTENSIONS constant."""
|
||||
|
||||
def test_supported_extensions_include_common_formats(self):
|
||||
"""Test that common document formats are included."""
|
||||
# Document formats
|
||||
assert ".pdf" in SUPPORTED_EXTENSIONS
|
||||
assert ".docx" in SUPPORTED_EXTENSIONS
|
||||
assert ".xlsx" in SUPPORTED_EXTENSIONS
|
||||
assert ".pptx" in SUPPORTED_EXTENSIONS
|
||||
|
||||
# Text formats
|
||||
assert ".txt" in SUPPORTED_EXTENSIONS
|
||||
assert ".md" in SUPPORTED_EXTENSIONS
|
||||
assert ".csv" in SUPPORTED_EXTENSIONS
|
||||
assert ".json" in SUPPORTED_EXTENSIONS
|
||||
assert ".yaml" in SUPPORTED_EXTENSIONS
|
||||
assert ".yml" in SUPPORTED_EXTENSIONS
|
||||
|
||||
# Image formats
|
||||
assert ".png" in SUPPORTED_EXTENSIONS
|
||||
assert ".jpg" in SUPPORTED_EXTENSIONS
|
||||
assert ".jpeg" in SUPPORTED_EXTENSIONS
|
||||
|
||||
|
||||
class TestExtractText:
|
||||
"""Test the extract_text function."""
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import os
|
||||
import pytest
|
||||
|
||||
from nanobot.agent.tools import file_state
|
||||
from nanobot.agent.tools.filesystem import EditFileTool, ReadFileTool, _find_match
|
||||
from nanobot.agent.tools.filesystem import EditFileTool, ReadFileTool
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -68,41 +68,6 @@ class TestDeleteLineCleanup:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestSmartQuoteNormalization:
|
||||
"""_find_match should handle curly ↔ straight quote fallback."""
|
||||
|
||||
def test_curly_double_quotes_match_straight(self):
|
||||
content = 'She said \u201chello\u201d to him'
|
||||
old_text = 'She said "hello" to him'
|
||||
match, count = _find_match(content, old_text)
|
||||
assert match is not None
|
||||
assert count == 1
|
||||
# Returned match should be the ORIGINAL content with curly quotes
|
||||
assert "\u201c" in match
|
||||
|
||||
def test_curly_single_quotes_match_straight(self):
|
||||
content = "it\u2019s a test"
|
||||
old_text = "it's a test"
|
||||
match, count = _find_match(content, old_text)
|
||||
assert match is not None
|
||||
assert count == 1
|
||||
assert "\u2019" in match
|
||||
|
||||
def test_straight_matches_curly_in_old_text(self):
|
||||
content = 'x = "hello"'
|
||||
old_text = 'x = \u201chello\u201d'
|
||||
match, count = _find_match(content, old_text)
|
||||
assert match is not None
|
||||
assert count == 1
|
||||
|
||||
def test_exact_match_still_preferred_over_quote_normalization(self):
|
||||
content = 'x = "hello"'
|
||||
old_text = 'x = "hello"'
|
||||
match, count = _find_match(content, old_text)
|
||||
assert match == old_text
|
||||
assert count == 1
|
||||
|
||||
|
||||
class TestQuoteStylePreservation:
|
||||
"""When quote-normalized matching occurs, replacement should preserve actual quote style."""
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from nanobot.agent.tools.filesystem import (
|
||||
ListDirTool,
|
||||
ReadFileTool,
|
||||
WriteFileTool,
|
||||
_find_match,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -116,52 +115,6 @@ class TestReadFileTool:
|
||||
assert "Maximum is 100 MiB" in result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _find_match (unit tests for the helper)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFindMatch:
|
||||
|
||||
def test_exact_match(self):
|
||||
match, count = _find_match("hello world", "world")
|
||||
assert match == "world"
|
||||
assert count == 1
|
||||
|
||||
def test_exact_no_match(self):
|
||||
match, count = _find_match("hello world", "xyz")
|
||||
assert match is None
|
||||
assert count == 0
|
||||
|
||||
def test_crlf_normalisation(self):
|
||||
# Caller normalises CRLF before calling _find_match, so test with
|
||||
# pre-normalised content to verify exact match still works.
|
||||
content = "line1\nline2\nline3"
|
||||
old_text = "line1\nline2\nline3"
|
||||
match, count = _find_match(content, old_text)
|
||||
assert match is not None
|
||||
assert count == 1
|
||||
|
||||
def test_line_trim_fallback(self):
|
||||
content = " def foo():\n pass\n"
|
||||
old_text = "def foo():\n pass"
|
||||
match, count = _find_match(content, old_text)
|
||||
assert match is not None
|
||||
assert count == 1
|
||||
# The returned match should be the *original* indented text
|
||||
assert " def foo():" in match
|
||||
|
||||
def test_line_trim_multiple_candidates(self):
|
||||
content = " a\n b\n a\n b\n"
|
||||
old_text = "a\nb"
|
||||
match, count = _find_match(content, old_text)
|
||||
assert count == 2
|
||||
|
||||
def test_empty_old_text(self):
|
||||
match, count = _find_match("hello", "")
|
||||
# Empty string is always "in" any string via exact match
|
||||
assert match == ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# EditFileTool
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -9,7 +9,6 @@ from nanobot.utils.file_edit_events import (
|
||||
build_file_edit_start_event,
|
||||
build_unified_diff_payload,
|
||||
line_diff_stats,
|
||||
prepare_file_edit_tracker,
|
||||
prepare_file_edit_trackers,
|
||||
read_file_snapshot,
|
||||
)
|
||||
@@ -44,15 +43,14 @@ def test_write_file_start_tracks_snapshot_and_end_emits_exact_diff(tmp_path: Pat
|
||||
target = tmp_path / "notes.txt"
|
||||
target.write_text("old\nkeep\n", encoding="utf-8")
|
||||
params = {"path": "notes.txt", "content": "new\nkeep\nextra\n"}
|
||||
tracker = prepare_file_edit_tracker(
|
||||
trackers = prepare_file_edit_trackers(
|
||||
call_id="call-write",
|
||||
tool_name="write_file",
|
||||
tool=_write_tool(tmp_path),
|
||||
workspace=tmp_path,
|
||||
params=params,
|
||||
)
|
||||
|
||||
assert tracker is not None
|
||||
[tracker] = trackers
|
||||
start = build_file_edit_start_event(tracker)
|
||||
assert start == {
|
||||
"version": 1,
|
||||
@@ -103,15 +101,14 @@ def test_unified_diff_payload_truncates_large_diffs() -> None:
|
||||
def test_binary_file_is_reported_but_not_counted(tmp_path: Path) -> None:
|
||||
target = tmp_path / "data.bin"
|
||||
target.write_bytes(b"\x00\x01before")
|
||||
tracker = prepare_file_edit_tracker(
|
||||
trackers = prepare_file_edit_trackers(
|
||||
call_id="call-bin",
|
||||
tool_name="edit_file",
|
||||
tool=_edit_tool(tmp_path),
|
||||
workspace=tmp_path,
|
||||
params={"path": "data.bin", "old_text": "before", "new_text": "after"},
|
||||
)
|
||||
|
||||
assert tracker is not None
|
||||
[tracker] = trackers
|
||||
assert not read_file_snapshot(target).countable
|
||||
target.write_bytes(b"\x00\x01after")
|
||||
event = build_file_edit_end_event(tracker)
|
||||
@@ -123,15 +120,14 @@ def test_binary_file_is_reported_but_not_counted(tmp_path: Path) -> None:
|
||||
def test_binary_before_file_is_reported_but_not_counted(tmp_path: Path) -> None:
|
||||
target = tmp_path / "data.bin"
|
||||
target.write_bytes(b"\x00\x01before")
|
||||
tracker = prepare_file_edit_tracker(
|
||||
trackers = prepare_file_edit_trackers(
|
||||
call_id="call-bin",
|
||||
tool_name="write_file",
|
||||
tool=_write_tool(tmp_path),
|
||||
workspace=tmp_path,
|
||||
params={"path": "data.bin", "content": "after\n"},
|
||||
)
|
||||
|
||||
assert tracker is not None
|
||||
[tracker] = trackers
|
||||
target.write_text("after\n", encoding="utf-8")
|
||||
event = build_file_edit_end_event(tracker)
|
||||
assert event["binary"] is True
|
||||
@@ -215,15 +211,14 @@ def test_apply_patch_dry_run_does_not_prepare_file_edit_trackers(tmp_path: Path)
|
||||
def test_oversized_file_is_reported_but_not_counted(tmp_path: Path) -> None:
|
||||
target = tmp_path / "large.txt"
|
||||
params = {"path": "large.txt", "content": "x"}
|
||||
tracker = prepare_file_edit_tracker(
|
||||
trackers = prepare_file_edit_trackers(
|
||||
call_id="call-large",
|
||||
tool_name="write_file",
|
||||
tool=_write_tool(tmp_path),
|
||||
workspace=tmp_path,
|
||||
params=params,
|
||||
)
|
||||
|
||||
assert tracker is not None
|
||||
[tracker] = trackers
|
||||
target.write_text("x" * (2 * 1024 * 1024 + 1), encoding="utf-8")
|
||||
event = build_file_edit_end_event(tracker)
|
||||
assert event["binary"] is True
|
||||
@@ -232,11 +227,11 @@ def test_oversized_file_is_reported_but_not_counted(tmp_path: Path) -> None:
|
||||
assert "diff" not in event
|
||||
|
||||
|
||||
def test_untracked_tools_do_not_prepare_file_edit_tracker(tmp_path: Path) -> None:
|
||||
assert prepare_file_edit_tracker(
|
||||
def test_untracked_tools_do_not_prepare_file_edit_trackers(tmp_path: Path) -> None:
|
||||
assert prepare_file_edit_trackers(
|
||||
call_id="call-exec",
|
||||
tool_name="exec",
|
||||
tool=None,
|
||||
workspace=tmp_path,
|
||||
params={"path": "created-by-shell.txt"},
|
||||
) is None
|
||||
) == []
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"""Tests for GitStore — line_ages() and core git operations."""
|
||||
"""Tests for GitStore core operations."""
|
||||
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from nanobot.utils.gitstore import GitStore, GitStoreError
|
||||
from nanobot.utils.gitstore import GitStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -18,89 +17,6 @@ def git(tmp_path):
|
||||
return g
|
||||
|
||||
|
||||
class TestLineAges:
|
||||
def test_returns_empty_when_not_initialized(self, tmp_path):
|
||||
"""line_ages should return [] if the git repo is not initialized."""
|
||||
git = GitStore(tmp_path, tracked_files=["MEMORY.md"])
|
||||
assert git.line_ages("MEMORY.md") == []
|
||||
|
||||
def test_returns_empty_for_missing_file(self, git):
|
||||
"""line_ages should return [] for a file that doesn't exist."""
|
||||
assert git.line_ages("SOUL.md") == []
|
||||
|
||||
def test_returns_empty_for_empty_file(self, git, tmp_path):
|
||||
"""line_ages should return [] for an empty tracked file."""
|
||||
(tmp_path / "SOUL.md").write_text("", encoding="utf-8")
|
||||
git.auto_commit("empty soul")
|
||||
assert git.line_ages("SOUL.md") == []
|
||||
|
||||
def test_one_age_per_line(self, git, tmp_path):
|
||||
"""line_ages should return one entry per line in the file."""
|
||||
content = "# Memory\n\n## Section A\n- item 1\n"
|
||||
(tmp_path / "MEMORY.md").write_text(content, encoding="utf-8")
|
||||
git.auto_commit("initial")
|
||||
ages = git.line_ages("MEMORY.md")
|
||||
assert len(ages) == len(content.splitlines())
|
||||
|
||||
def test_fresh_lines_have_age_zero(self, git, tmp_path):
|
||||
"""Lines committed today should have age_days=0."""
|
||||
(tmp_path / "MEMORY.md").write_text("## A\n- x\n", encoding="utf-8")
|
||||
git.auto_commit("initial")
|
||||
ages = git.line_ages("MEMORY.md")
|
||||
assert all(a.age_days == 0 for a in ages)
|
||||
|
||||
def test_age_differentiates_across_days(self, git, tmp_path):
|
||||
"""Lines committed today should show correct age when 'now' is mocked forward."""
|
||||
(tmp_path / "MEMORY.md").write_text("## A\n- x\n", encoding="utf-8")
|
||||
git.auto_commit("initial")
|
||||
|
||||
future_now = datetime.now(tz=timezone.utc) + timedelta(days=30)
|
||||
with patch("nanobot.utils.gitstore.datetime") as mock_dt:
|
||||
mock_dt.now.return_value = future_now
|
||||
mock_dt.fromtimestamp = datetime.fromtimestamp
|
||||
ages = git.line_ages("MEMORY.md")
|
||||
|
||||
assert len(ages) == 2
|
||||
assert all(a.age_days == 30 for a in ages)
|
||||
|
||||
def test_annotate_failure_is_explicit(self, git, tmp_path):
|
||||
(tmp_path / "MEMORY.md").write_text("important\n", encoding="utf-8")
|
||||
git.auto_commit("initial")
|
||||
|
||||
with patch("dulwich.porcelain.annotate", side_effect=OSError("broken repo")):
|
||||
with pytest.raises(GitStoreError, match="annotation failed"):
|
||||
git.line_ages("MEMORY.md")
|
||||
|
||||
def test_partial_edit_only_updates_changed_lines(self, git, tmp_path):
|
||||
"""Only modified lines should reflect the new commit's timestamp."""
|
||||
now = datetime(2026, 5, 1, tzinfo=timezone.utc)
|
||||
old = now - timedelta(days=30)
|
||||
|
||||
(tmp_path / "MEMORY.md").write_text(
|
||||
"# Memory\n\n## A\n- old\n\n## B\n- keep\n", encoding="utf-8"
|
||||
)
|
||||
with patch("dulwich.worktree.time.time", return_value=old.timestamp()):
|
||||
git.auto_commit("commit1")
|
||||
|
||||
# Only modify section A
|
||||
(tmp_path / "MEMORY.md").write_text(
|
||||
"# Memory\n\n## A\n- new\n\n## B\n- keep\n", encoding="utf-8"
|
||||
)
|
||||
with patch("dulwich.worktree.time.time", return_value=now.timestamp()):
|
||||
git.auto_commit("commit2")
|
||||
|
||||
with patch("nanobot.utils.gitstore.datetime") as mock_dt:
|
||||
mock_dt.now.return_value = now
|
||||
mock_dt.fromtimestamp = datetime.fromtimestamp
|
||||
ages = git.line_ages("MEMORY.md")
|
||||
|
||||
lines = (tmp_path / "MEMORY.md").read_text(encoding="utf-8").splitlines()
|
||||
assert len(ages) == len(lines)
|
||||
age_by_line = {line: age.age_days for line, age in zip(lines, ages, strict=True)}
|
||||
assert age_by_line["- new"] == 0
|
||||
assert age_by_line["- keep"] == 30
|
||||
|
||||
|
||||
class TestSummarizeWorkingTree:
|
||||
"""Ground-truth diff summary used to keep Dream audit records honest."""
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfoNotFoundError
|
||||
|
||||
import pytest
|
||||
import tiktoken
|
||||
|
||||
from nanobot.utils import helpers
|
||||
from nanobot.utils.helpers import (
|
||||
_write_text_atomic,
|
||||
content_with_media_breadcrumbs,
|
||||
current_time_str,
|
||||
split_message,
|
||||
truncate_text_to_tokens,
|
||||
)
|
||||
@@ -51,11 +48,6 @@ def test_truncate_text_to_tokens_non_positive_budget_returns_text():
|
||||
assert truncate_text_to_tokens(text, 0) == text
|
||||
|
||||
|
||||
def test_current_time_str_rejects_unknown_timezone():
|
||||
with pytest.raises(ZoneInfoNotFoundError):
|
||||
current_time_str("Not/AZone")
|
||||
|
||||
|
||||
def test_content_with_media_breadcrumbs_preserves_valid_paths():
|
||||
assert content_with_media_breadcrumbs(
|
||||
"user",
|
||||
|
||||
Reference in New Issue
Block a user