Change-detectors, tautologies, source-reading tests, redundant duplicates, mock-echo tests and dead/unrunnable tests. Per-test rationale in the lane ledger (category + reason for every removal).
97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
"""Tests for trajectory_compressor AsyncOpenAI event loop binding.
|
|
|
|
The AsyncOpenAI client was created once at __init__ time and stored as an
|
|
instance attribute. When process_directory() calls asyncio.run() — which
|
|
creates and closes a fresh event loop — the client's internal httpx
|
|
transport remains bound to the now-closed loop. A second call to
|
|
process_directory() would fail with "Event loop is closed".
|
|
|
|
The fix creates the AsyncOpenAI client lazily via _get_async_client() so
|
|
each asyncio.run() gets a client bound to the current loop.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestAsyncClientLazyCreation:
|
|
"""trajectory_compressor.py — _get_async_client()"""
|
|
|
|
|
|
|
|
def test_get_async_client_creates_fresh_each_call(self):
|
|
"""Each call to _get_async_client() creates a NEW client instance,
|
|
so it binds to the current event loop."""
|
|
from trajectory_compressor import TrajectoryCompressor
|
|
|
|
comp = TrajectoryCompressor.__new__(TrajectoryCompressor)
|
|
comp.config = MagicMock()
|
|
comp.config.base_url = "https://api.example.com/v1"
|
|
comp._async_client_api_key = "test-key"
|
|
comp.async_client = None
|
|
|
|
call_count = 0
|
|
instances = []
|
|
|
|
def mock_constructor(**kwargs):
|
|
nonlocal call_count
|
|
call_count += 1
|
|
instance = MagicMock()
|
|
instances.append(instance)
|
|
return instance
|
|
|
|
with patch("openai.AsyncOpenAI", side_effect=mock_constructor):
|
|
comp._get_async_client()
|
|
comp._get_async_client()
|
|
|
|
# Should have created two separate instances
|
|
assert call_count == 2
|
|
assert instances[0] is not instances[1]
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_summary_async_kimi_omits_temperature():
|
|
"""Kimi models should have temperature omitted — server manages it."""
|
|
from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics
|
|
|
|
config = CompressionConfig(
|
|
summarization_model="kimi-for-coding",
|
|
temperature=0.3,
|
|
summary_target_tokens=100,
|
|
max_retries=1,
|
|
)
|
|
compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
|
|
compressor.config = config
|
|
compressor.logger = MagicMock()
|
|
compressor._use_call_llm = False
|
|
async_client = MagicMock()
|
|
async_client.chat.completions.create = MagicMock(return_value=SimpleNamespace(
|
|
choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
|
|
))
|
|
compressor._get_async_client = MagicMock(return_value=async_client)
|
|
|
|
metrics = TrajectoryMetrics()
|
|
result = await compressor._generate_summary_async("tool output", metrics)
|
|
|
|
assert result.startswith("[CONTEXT SUMMARY]:")
|
|
assert "temperature" not in async_client.chat.completions.create.call_args.kwargs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_entry_async_passes_non_dict_through():
|
|
"""A scalar JSONL line used to crash on '"conversations" not in entry';
|
|
unknown shapes pass through byte-faithful."""
|
|
from trajectory_compressor import TrajectoryCompressor
|
|
|
|
compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
|
|
entry, metrics = await compressor.process_entry_async(42)
|
|
assert entry == 42
|