test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Tests for Curator class methods — no live LLM or Qdrant required."""
|
|
|
|
|
import pytest
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def make_curator():
|
|
|
|
|
"""Return a Curator instance with load_curator_prompt mocked and mock QdrantService."""
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
from app.curator import Curator
|
|
|
|
|
|
|
|
|
|
mock_qdrant = MagicMock()
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
with patch("app.curator.load_curator_prompt", return_value="Curate memories. Date: {CURRENT_DATE}"):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
curator = Curator(
|
|
|
|
|
qdrant_service=mock_qdrant,
|
|
|
|
|
model="test-model",
|
|
|
|
|
ollama_host="http://localhost:11434",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return curator, mock_qdrant
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestParseJsonResponse:
|
|
|
|
|
"""Tests for Curator._parse_json_response."""
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_direct_valid_json(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Valid JSON string parsed directly."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
payload = {"new_curated_turns": [], "deletions": []}
|
|
|
|
|
result = curator._parse_json_response(json.dumps(payload))
|
|
|
|
|
assert result == payload
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_json_in_code_block(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""JSON wrapped in ```json ... ``` code fence is extracted."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
payload = {"summary": "done"}
|
|
|
|
|
response = f"```json\n{json.dumps(payload)}\n```"
|
|
|
|
|
result = curator._parse_json_response(response)
|
|
|
|
|
assert result == payload
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_json_embedded_in_text(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""JSON embedded after prose text is extracted via brace scan."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
payload = {"new_curated_turns": [{"content": "Q: hi\nA: there"}]}
|
|
|
|
|
response = f"Here is the result:\n{json.dumps(payload)}\nThat's all."
|
|
|
|
|
result = curator._parse_json_response(response)
|
|
|
|
|
assert result is not None
|
|
|
|
|
assert "new_curated_turns" in result
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_empty_string_returns_none(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Empty response returns None."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
result = curator._parse_json_response("")
|
|
|
|
|
assert result is None
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_malformed_json_returns_none(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Completely invalid text returns None."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
result = curator._parse_json_response("this is not json at all !!!")
|
|
|
|
|
assert result is None
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_json_in_plain_code_block(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""JSON in ``` (no language tag) code fence is extracted."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
payload = {"permanent_rules": []}
|
|
|
|
|
response = f"```\n{json.dumps(payload)}\n```"
|
|
|
|
|
result = curator._parse_json_response(response)
|
|
|
|
|
assert result == payload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestIsRecent:
|
|
|
|
|
"""Tests for Curator._is_recent."""
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_memory_within_window(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Memory timestamped 1 hour ago is recent (within 24h)."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
ts = (datetime.utcnow() - timedelta(hours=1)).isoformat() + "Z"
|
|
|
|
|
memory = {"timestamp": ts}
|
|
|
|
|
assert curator._is_recent(memory, hours=24) is True
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_memory_outside_window(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Memory timestamped 48 hours ago is not recent."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
ts = (datetime.utcnow() - timedelta(hours=48)).isoformat() + "Z"
|
|
|
|
|
memory = {"timestamp": ts}
|
|
|
|
|
assert curator._is_recent(memory, hours=24) is False
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_no_timestamp_returns_true(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Memory without timestamp is treated as recent (safe default)."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
memory = {}
|
|
|
|
|
assert curator._is_recent(memory, hours=24) is True
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_empty_timestamp_returns_true(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Memory with empty timestamp string is treated as recent."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
memory = {"timestamp": ""}
|
|
|
|
|
assert curator._is_recent(memory, hours=24) is True
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_unparseable_timestamp_returns_true(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Memory with garbage timestamp is treated as recent (safe default)."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
memory = {"timestamp": "not-a-date"}
|
|
|
|
|
assert curator._is_recent(memory, hours=24) is True
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_boundary_edge_just_inside(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Memory at exactly hours-1 minutes ago should be recent."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
ts = (datetime.utcnow() - timedelta(hours=23, minutes=59)).isoformat() + "Z"
|
|
|
|
|
memory = {"timestamp": ts}
|
|
|
|
|
assert curator._is_recent(memory, hours=24) is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestFormatRawTurns:
|
|
|
|
|
"""Tests for Curator._format_raw_turns."""
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_empty_list(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Empty input produces empty string."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
result = curator._format_raw_turns([])
|
|
|
|
|
assert result == ""
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_single_turn_header(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Single turn has RAW TURN 1 header and turn ID."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
turns = [{"id": "abc123", "text": "User: hello\nAssistant: hi"}]
|
|
|
|
|
result = curator._format_raw_turns(turns)
|
|
|
|
|
assert "RAW TURN 1" in result
|
|
|
|
|
assert "abc123" in result
|
|
|
|
|
assert "hello" in result
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_multiple_turns_numbered(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Multiple turns are numbered sequentially."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
turns = [
|
|
|
|
|
{"id": "id1", "text": "turn one"},
|
|
|
|
|
{"id": "id2", "text": "turn two"},
|
|
|
|
|
{"id": "id3", "text": "turn three"},
|
|
|
|
|
]
|
|
|
|
|
result = curator._format_raw_turns(turns)
|
|
|
|
|
assert "RAW TURN 1" in result
|
|
|
|
|
assert "RAW TURN 2" in result
|
|
|
|
|
assert "RAW TURN 3" in result
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
def test_missing_id_uses_unknown(self):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
"""Turn without id field shows 'unknown' placeholder."""
|
2026-03-31 19:32:27 -05:00
|
|
|
curator, _ = make_curator()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
turns = [{"text": "some text"}]
|
|
|
|
|
result = curator._format_raw_turns(turns)
|
|
|
|
|
assert "unknown" in result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestAppendRuleToFile:
|
2026-03-31 19:32:27 -05:00
|
|
|
"""Tests for Curator._append_rule_to_file (filesystem via tmp_path)."""
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_appends_to_existing_file(self, tmp_path):
|
|
|
|
|
"""Rule is appended to existing file."""
|
2026-03-31 19:32:27 -05:00
|
|
|
import app.curator as curator_module
|
|
|
|
|
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
prompts_dir = tmp_path / "prompts"
|
|
|
|
|
prompts_dir.mkdir()
|
|
|
|
|
target = prompts_dir / "systemprompt.md"
|
|
|
|
|
target.write_text("# Existing content\n")
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
with patch("app.curator.load_curator_prompt", return_value="prompt {CURRENT_DATE}"), \
|
|
|
|
|
patch.object(curator_module, "PROMPTS_DIR", prompts_dir):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
from app.curator import Curator
|
|
|
|
|
mock_qdrant = MagicMock()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
curator = Curator(mock_qdrant, model="m", ollama_host="http://x")
|
|
|
|
|
await curator._append_rule_to_file("systemprompt.md", "Always be concise.")
|
|
|
|
|
|
|
|
|
|
content = target.read_text()
|
|
|
|
|
assert "Always be concise." in content
|
|
|
|
|
assert "# Existing content" in content
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_creates_file_if_missing(self, tmp_path):
|
|
|
|
|
"""Rule is written to a new file if none existed."""
|
2026-03-31 19:32:27 -05:00
|
|
|
import app.curator as curator_module
|
|
|
|
|
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
prompts_dir = tmp_path / "prompts"
|
|
|
|
|
prompts_dir.mkdir()
|
|
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
with patch("app.curator.load_curator_prompt", return_value="prompt {CURRENT_DATE}"), \
|
|
|
|
|
patch.object(curator_module, "PROMPTS_DIR", prompts_dir):
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
|
2026-03-31 19:32:27 -05:00
|
|
|
from app.curator import Curator
|
|
|
|
|
mock_qdrant = MagicMock()
|
test: expand coverage to 70%+ — add utils, config, curator, proxy, integration tests
- Extend test_utils.py: filter_memories_by_time, merge_memories, calculate_token_budget, build_augmented_messages (mocked)
- Extend test_config.py: Config.load() with TOML via tmp_path, CloudConfig helpers, env var api_key
- Add test_curator.py: _parse_json_response, _is_recent, _format_raw_turns, _append_rule_to_file
- Add test_proxy_handler.py: clean_message_content, handle_chat_non_streaming (mocked httpx+qdrant)
- Add test_integration.py: health check, /api/tags, /api/chat non-streaming + streaming via TestClient
- Add pytest.ini (asyncio_mode=auto), add pytest-cov to requirements.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:19:49 -05:00
|
|
|
curator = Curator(mock_qdrant, model="m", ollama_host="http://x")
|
|
|
|
|
await curator._append_rule_to_file("newfile.md", "New rule here.")
|
|
|
|
|
|
|
|
|
|
target = prompts_dir / "newfile.md"
|
|
|
|
|
assert target.exists()
|
|
|
|
|
assert "New rule here." in target.read_text()
|