chore: fix ruff linting and async patterns in tests/
This commit is contained in:
@@ -58,7 +58,7 @@ class TestCheckCcproxyAuth:
|
||||
mock_run.return_value = MagicMock(
|
||||
returncode=0, stdout="Authenticated", stderr=""
|
||||
)
|
||||
valid, msg = check_ccproxy_auth("codex")
|
||||
valid, _msg = check_ccproxy_auth("codex")
|
||||
assert valid is True
|
||||
cmd = mock_run.call_args[0][0]
|
||||
assert cmd[1:] == ["auth", "status", "codex"]
|
||||
|
||||
@@ -249,7 +249,7 @@ class TestRetryAsync:
|
||||
)
|
||||
# With 50% jitter, not all delays should be identical
|
||||
if len(delays) > 1:
|
||||
assert len(set(f"{d:.4f}" for d in delays)) > 1
|
||||
assert len({f"{d:.4f}" for d in delays}) > 1
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
@@ -1438,10 +1438,10 @@ class TestInboundConsumer:
|
||||
async def _test():
|
||||
consumer = self._make_consumer()
|
||||
# Start and immediately stop
|
||||
asyncio.create_task(consumer.run())
|
||||
task = asyncio.create_task(consumer.run())
|
||||
await asyncio.sleep(0.1)
|
||||
await consumer.stop()
|
||||
await asyncio.sleep(0.1)
|
||||
await task
|
||||
assert consumer._stopping is True
|
||||
|
||||
_run(_test())
|
||||
|
||||
@@ -1,38 +1,29 @@
|
||||
"""Tests for the /compact command (compact_conversation helper)."""
|
||||
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _run():
|
||||
"""Helper to run async tests."""
|
||||
loop = asyncio.new_event_loop()
|
||||
yield loop.run_until_complete
|
||||
loop.close()
|
||||
from tests.conftest import run_async as _run
|
||||
|
||||
|
||||
class TestCompactGuards:
|
||||
"""Guard conditions that return early without touching the middleware."""
|
||||
|
||||
def test_no_agent(self, _run):
|
||||
def test_no_agent(self):
|
||||
from EvoScientist.cli.commands import compact_conversation
|
||||
|
||||
result = _run(compact_conversation(agent=None, thread_id="abc"))
|
||||
assert result.status == "noop"
|
||||
assert "Nothing to compact" in result.message
|
||||
|
||||
def test_no_thread_id(self, _run):
|
||||
def test_no_thread_id(self):
|
||||
from EvoScientist.cli.commands import compact_conversation
|
||||
|
||||
result = _run(compact_conversation(agent=MagicMock(), thread_id=None))
|
||||
assert result.status == "noop"
|
||||
assert "Nothing to compact" in result.message
|
||||
|
||||
def test_empty_messages(self, _run):
|
||||
def test_empty_messages(self):
|
||||
from EvoScientist.cli.commands import compact_conversation
|
||||
|
||||
agent = MagicMock()
|
||||
@@ -43,7 +34,7 @@ class TestCompactGuards:
|
||||
assert result.status == "noop"
|
||||
assert "no messages" in result.message
|
||||
|
||||
def test_state_read_failure(self, _run):
|
||||
def test_state_read_failure(self):
|
||||
from EvoScientist.cli.commands import compact_conversation
|
||||
|
||||
agent = MagicMock()
|
||||
@@ -57,7 +48,7 @@ class TestCompactGuards:
|
||||
class TestCompactCutoffZero:
|
||||
"""When cutoff == 0, conversation is within retention budget."""
|
||||
|
||||
def test_nothing_to_compact_short_conversation(self, _run):
|
||||
def test_nothing_to_compact_short_conversation(self):
|
||||
from EvoScientist.cli.commands import compact_conversation
|
||||
|
||||
agent = MagicMock()
|
||||
@@ -102,7 +93,7 @@ class TestCompactCutoffZero:
|
||||
class TestCompactNegligibleSavings:
|
||||
"""When cutoff > 0 but savings are too small to be worth it."""
|
||||
|
||||
def test_skip_when_few_messages_and_low_tokens(self, _run):
|
||||
def test_skip_when_few_messages_and_low_tokens(self):
|
||||
from EvoScientist.cli.commands import compact_conversation
|
||||
|
||||
agent = MagicMock()
|
||||
@@ -151,7 +142,7 @@ class TestCompactNegligibleSavings:
|
||||
# No LLM call should have been made
|
||||
mock_middleware_inst._acreate_summary.assert_not_called()
|
||||
|
||||
def test_still_compacts_when_few_messages_but_high_tokens(self, _run):
|
||||
def test_still_compacts_when_few_messages_but_high_tokens(self):
|
||||
"""2 messages but they account for >2% of tokens — should compact."""
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
@@ -211,7 +202,7 @@ class TestCompactNegligibleSavings:
|
||||
class TestCompactSuccess:
|
||||
"""Normal compaction flow."""
|
||||
|
||||
def test_successful_compaction(self, _run):
|
||||
def test_successful_compaction(self):
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
from EvoScientist.cli.commands import compact_conversation
|
||||
@@ -281,7 +272,7 @@ class TestCompactSuccess:
|
||||
assert "_summarization_event" in event_data
|
||||
assert event_data["_summarization_event"]["cutoff_index"] == 15
|
||||
|
||||
def test_offload_failure_non_fatal(self, _run):
|
||||
def test_offload_failure_non_fatal(self):
|
||||
"""Offload failure should not prevent compaction."""
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
|
||||
@@ -283,11 +283,11 @@ class TestDingTalkProbe:
|
||||
def test_missing_client_id(self):
|
||||
from EvoScientist.channels.dingtalk.probe import validate_dingtalk
|
||||
|
||||
ok, msg = _run(validate_dingtalk("", "secret"))
|
||||
ok, _msg = _run(validate_dingtalk("", "secret"))
|
||||
assert ok is False
|
||||
|
||||
def test_missing_client_secret(self):
|
||||
from EvoScientist.channels.dingtalk.probe import validate_dingtalk
|
||||
|
||||
ok, msg = _run(validate_dingtalk("id", ""))
|
||||
ok, _msg = _run(validate_dingtalk("id", ""))
|
||||
assert ok is False
|
||||
|
||||
+1
-1
@@ -580,7 +580,7 @@ class TestMiniMaxProvider:
|
||||
def test_minimax_model_ids_correct(self):
|
||||
"""MiniMax model IDs should match the official API model names."""
|
||||
minimax_models = get_models_for_provider("minimax")
|
||||
model_dict = {name: mid for name, mid in minimax_models}
|
||||
model_dict = dict(minimax_models)
|
||||
assert model_dict["minimax-m2.7"] == "MiniMax-M2.7"
|
||||
assert model_dict["minimax-m2.5"] == "MiniMax-M2.5"
|
||||
assert model_dict["minimax-m2.5-highspeed"] == "MiniMax-M2.5-highspeed"
|
||||
|
||||
@@ -556,7 +556,8 @@ class TestAddMcpServer:
|
||||
add_mcp_server("a", "stdio", command="cmd1")
|
||||
add_mcp_server("b", "http", url="http://x")
|
||||
data = yaml.safe_load(user_mcp_dir.read_text())
|
||||
assert "a" in data and "b" in data
|
||||
assert "a" in data
|
||||
assert "b" in data
|
||||
|
||||
|
||||
class TestRemoveMcpServer:
|
||||
|
||||
+37
-37
@@ -25,7 +25,7 @@ from tests.conftest import run_async as _run
|
||||
class TestGenerateThreadId(unittest.TestCase):
|
||||
def test_length(self):
|
||||
tid = generate_thread_id()
|
||||
self.assertEqual(len(tid), 8)
|
||||
assert len(tid) == 8
|
||||
|
||||
def test_hex(self):
|
||||
tid = generate_thread_id()
|
||||
@@ -33,58 +33,58 @@ class TestGenerateThreadId(unittest.TestCase):
|
||||
|
||||
def test_uniqueness(self):
|
||||
ids = {generate_thread_id() for _ in range(100)}
|
||||
self.assertEqual(len(ids), 100)
|
||||
assert len(ids) == 100
|
||||
|
||||
|
||||
class TestGetDbPath(unittest.TestCase):
|
||||
def test_uses_config_dir(self):
|
||||
path = get_db_path()
|
||||
self.assertTrue(str(path).endswith("sessions.db"))
|
||||
self.assertIn(".config", str(path))
|
||||
self.assertIn("evoscientist", str(path))
|
||||
assert str(path).endswith("sessions.db")
|
||||
assert ".config" in str(path)
|
||||
assert "evoscientist" in str(path)
|
||||
|
||||
|
||||
class TestFormatRelativeTime(unittest.TestCase):
|
||||
def test_none(self):
|
||||
self.assertEqual(_format_relative_time(None), "")
|
||||
assert _format_relative_time(None) == ""
|
||||
|
||||
def test_invalid(self):
|
||||
self.assertEqual(_format_relative_time("not-a-date"), "")
|
||||
assert _format_relative_time("not-a-date") == ""
|
||||
|
||||
def test_recent(self):
|
||||
from datetime import datetime
|
||||
|
||||
now = datetime.now(UTC).isoformat()
|
||||
result = _format_relative_time(now)
|
||||
self.assertIn("just now", result)
|
||||
assert "just now" in result
|
||||
|
||||
def test_minutes(self):
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
ts = (datetime.now(UTC) - timedelta(minutes=5)).isoformat()
|
||||
result = _format_relative_time(ts)
|
||||
self.assertIn("min ago", result)
|
||||
assert "min ago" in result
|
||||
|
||||
def test_hours(self):
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
ts = (datetime.now(UTC) - timedelta(hours=2)).isoformat()
|
||||
result = _format_relative_time(ts)
|
||||
self.assertIn("hour", result)
|
||||
assert "hour" in result
|
||||
|
||||
def test_days(self):
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
ts = (datetime.now(UTC) - timedelta(days=3)).isoformat()
|
||||
result = _format_relative_time(ts)
|
||||
self.assertIn("day", result)
|
||||
assert "day" in result
|
||||
|
||||
def test_months(self):
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
ts = (datetime.now(UTC) - timedelta(days=65)).isoformat()
|
||||
result = _format_relative_time(ts)
|
||||
self.assertIn("month", result)
|
||||
assert "month" in result
|
||||
|
||||
|
||||
class TestThreadFunctions(unittest.TestCase):
|
||||
@@ -183,44 +183,44 @@ class TestThreadFunctions(unittest.TestCase):
|
||||
def test_list_threads(self):
|
||||
threads = _run(list_threads(limit=10))
|
||||
# Should only contain EvoScientist threads
|
||||
self.assertEqual(len(threads), 3)
|
||||
assert len(threads) == 3
|
||||
# Most recent first
|
||||
self.assertEqual(threads[0]["thread_id"], "def00001")
|
||||
assert threads[0]["thread_id"] == "def00001"
|
||||
|
||||
def test_list_threads_with_message_count(self):
|
||||
threads = _run(list_threads(limit=10, include_message_count=True))
|
||||
self.assertIn("message_count", threads[0])
|
||||
assert "message_count" in threads[0]
|
||||
|
||||
def test_thread_exists_true(self):
|
||||
self.assertTrue(_run(thread_exists("abc12345")))
|
||||
assert _run(thread_exists("abc12345"))
|
||||
|
||||
def test_thread_exists_false(self):
|
||||
self.assertFalse(_run(thread_exists("nonexist")))
|
||||
assert not _run(thread_exists("nonexist"))
|
||||
|
||||
def test_find_similar(self):
|
||||
similar = _run(find_similar_threads("abc1"))
|
||||
self.assertEqual(len(similar), 2)
|
||||
self.assertIn("abc12345", similar)
|
||||
self.assertIn("abc12399", similar)
|
||||
assert len(similar) == 2
|
||||
assert "abc12345" in similar
|
||||
assert "abc12399" in similar
|
||||
|
||||
def test_find_similar_no_match(self):
|
||||
similar = _run(find_similar_threads("xyz"))
|
||||
self.assertEqual(len(similar), 0)
|
||||
assert len(similar) == 0
|
||||
|
||||
def test_get_most_recent(self):
|
||||
recent = _run(get_most_recent())
|
||||
self.assertIsNotNone(recent)
|
||||
self.assertEqual(recent, "def00001")
|
||||
assert recent is not None
|
||||
assert recent == "def00001"
|
||||
|
||||
def test_get_thread_metadata(self):
|
||||
meta = _run(get_thread_metadata("abc12345"))
|
||||
self.assertIsNotNone(meta)
|
||||
self.assertEqual(meta["workspace_dir"], "/tmp/ws_abc12345")
|
||||
self.assertEqual(meta["model"], "claude-sonnet-4-5")
|
||||
assert meta is not None
|
||||
assert meta["workspace_dir"] == "/tmp/ws_abc12345"
|
||||
assert meta["model"] == "claude-sonnet-4-5"
|
||||
|
||||
def test_get_thread_metadata_missing(self):
|
||||
meta = _run(get_thread_metadata("nonexist"))
|
||||
self.assertIsNone(meta)
|
||||
assert meta is None
|
||||
|
||||
def test_delete_thread(self):
|
||||
# Insert a thread to delete
|
||||
@@ -242,29 +242,29 @@ class TestThreadFunctions(unittest.TestCase):
|
||||
|
||||
_run(_insert())
|
||||
|
||||
self.assertTrue(_run(thread_exists("todelete")))
|
||||
self.assertTrue(_run(delete_thread("todelete")))
|
||||
self.assertFalse(_run(thread_exists("todelete")))
|
||||
assert _run(thread_exists("todelete"))
|
||||
assert _run(delete_thread("todelete"))
|
||||
assert not _run(thread_exists("todelete"))
|
||||
|
||||
def test_delete_nonexistent(self):
|
||||
self.assertFalse(_run(delete_thread("nope1234")))
|
||||
assert not _run(delete_thread("nope1234"))
|
||||
|
||||
# -- Agent isolation: OtherAgent data should never be visible --
|
||||
|
||||
def test_thread_exists_ignores_other_agent(self):
|
||||
self.assertFalse(_run(thread_exists("zzz99999")))
|
||||
assert not _run(thread_exists("zzz99999"))
|
||||
|
||||
def test_find_similar_ignores_other_agent(self):
|
||||
similar = _run(find_similar_threads("zzz"))
|
||||
self.assertEqual(len(similar), 0)
|
||||
assert len(similar) == 0
|
||||
|
||||
def test_get_metadata_ignores_other_agent(self):
|
||||
meta = _run(get_thread_metadata("zzz99999"))
|
||||
self.assertIsNone(meta)
|
||||
assert meta is None
|
||||
|
||||
def test_delete_ignores_other_agent(self):
|
||||
# Should not delete OtherAgent's data
|
||||
self.assertFalse(_run(delete_thread("zzz99999")))
|
||||
assert not _run(delete_thread("zzz99999"))
|
||||
|
||||
def test_delete_thread_preserves_other_agent_writes(self):
|
||||
"""Deleting a shared thread_id must only remove writes linked to
|
||||
@@ -329,8 +329,8 @@ class TestThreadFunctions(unittest.TestCase):
|
||||
return [r[0] for r in rows]
|
||||
|
||||
remaining = _run(_check())
|
||||
self.assertIn("cp_other_shared", remaining)
|
||||
self.assertNotIn("cp_evo_shared", remaining)
|
||||
assert "cp_other_shared" in remaining
|
||||
assert "cp_evo_shared" not in remaining
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+16
-11
@@ -203,10 +203,11 @@ class TestExtractSummarizationText:
|
||||
|
||||
def test_content_blocks(self):
|
||||
class Msg:
|
||||
content = [
|
||||
{"type": "text", "text": "part1"},
|
||||
{"type": "text", "text": "part2"},
|
||||
]
|
||||
def __init__(self):
|
||||
self.content = [
|
||||
{"type": "text", "text": "part1"},
|
||||
{"type": "text", "text": "part2"},
|
||||
]
|
||||
|
||||
assert _extract_summarization_text(Msg()) == "part1part2"
|
||||
|
||||
@@ -214,13 +215,15 @@ class TestExtractSummarizationText:
|
||||
"""Content blocks may include 'index' field — should still extract text."""
|
||||
|
||||
class Msg:
|
||||
content = [{"type": "text", "text": " vs", "index": 1}]
|
||||
def __init__(self):
|
||||
self.content = [{"type": "text", "text": " vs", "index": 1}]
|
||||
|
||||
assert _extract_summarization_text(Msg()) == " vs"
|
||||
|
||||
def test_empty_list(self):
|
||||
class Msg:
|
||||
content = []
|
||||
def __init__(self):
|
||||
self.content = []
|
||||
|
||||
assert _extract_summarization_text(Msg()) == ""
|
||||
|
||||
@@ -232,15 +235,17 @@ class TestExtractSummarizationText:
|
||||
|
||||
def test_mixed_block_types(self):
|
||||
class Msg:
|
||||
content = [
|
||||
{"type": "text", "text": "hello"},
|
||||
{"type": "image", "url": "..."},
|
||||
]
|
||||
def __init__(self):
|
||||
self.content = [
|
||||
{"type": "text", "text": "hello"},
|
||||
{"type": "image", "url": "..."},
|
||||
]
|
||||
|
||||
assert _extract_summarization_text(Msg()) == "hello"
|
||||
|
||||
def test_string_blocks_in_list(self):
|
||||
class Msg:
|
||||
content = ["hello", "world"]
|
||||
def __init__(self):
|
||||
self.content = ["hello", "world"]
|
||||
|
||||
assert _extract_summarization_text(Msg()) == "helloworld"
|
||||
|
||||
@@ -306,7 +306,7 @@ class TestWeChatCrypto:
|
||||
# Parse and verify the encrypted content decrypts back
|
||||
root = ET.fromstring(xml_reply)
|
||||
encrypt = root.find("Encrypt").text
|
||||
decrypted, app_id = crypto.decrypt(encrypt)
|
||||
decrypted, _app_id = crypto.decrypt(encrypt)
|
||||
assert decrypted == msg
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user