fix: 5 agents — core 703/703, settings 38/38, git edges 191/191, API routes 1139/1140, plugins +70 coverage

This commit is contained in:
2026-06-15 18:02:09 +03:00
parent 9cf2d6400a
commit 3de67c258a
18 changed files with 1432 additions and 154 deletions

View File

@@ -0,0 +1,207 @@
# #region Test.LLMParse [C:3] [TYPE Module] [SEMANTICS test, translate, llm, parse, json, recovery]
# @BRIEF Tests for _llm_parse.py — parse_llm_response, markdown recovery, truncated row recovery.
# @RELATION BINDS_TO -> [_llm_parse]
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import json
import pytest
from src.plugins.translate._llm_parse import (
parse_llm_response,
_recover_from_markdown,
_recover_truncated_rows,
)
class TestRecoverFromMarkdown:
"""_recover_from_markdown — extract JSON from markdown code blocks."""
def test_json_code_block(self):
"""Standard ```json code block."""
text = "```json\n{\"rows\": [{\"row_id\": 1, \"translation\": \"hola\"}]}\n```"
result = _recover_from_markdown(text)
assert result is not None
assert result["rows"][0]["row_id"] == 1
def test_no_marker_code_block(self):
"""Code block without json marker."""
text = "```\n{\"rows\": [{\"row_id\": 1}]}\n```"
result = _recover_from_markdown(text)
assert result is not None
assert len(result["rows"]) == 1
def test_no_code_block(self):
"""Plain text without code block."""
result = _recover_from_markdown("just plain text")
assert result is None
def test_invalid_json_in_block(self):
"""Code block with invalid JSON returns None."""
text = "```json\n{invalid json}\n```"
result = _recover_from_markdown(text)
assert result is None
def test_empty_block(self):
"""Empty code block returns None."""
text = "```json\n```"
result = _recover_from_markdown(text)
assert result is None
def test_inline_text_before_after(self):
"""Text before and after code block still extracts JSON."""
text = "some text\n```json\n{\"rows\": []}\n```\nmore text"
result = _recover_from_markdown(text)
assert result is not None
class TestRecoverTruncatedRows:
"""_recover_truncated_rows — extract complete rows from truncated JSON."""
def test_recover_single_row(self):
"""Single complete row extracted from truncated text."""
text = 'some text {"row_id": 1, "translation": "hola"} more text'
result = _recover_truncated_rows(text, 1, "length")
assert result is not None
assert len(result["rows"]) == 1
def test_recover_multiple_rows(self):
"""Multiple rows extracted."""
text = '{"row_id": 1, "translation": "hola"} {"row_id": 2, "translation": "adios"}'
result = _recover_truncated_rows(text, 2, "length")
assert result is not None
assert len(result["rows"]) == 2
def test_no_rows_found(self):
"""No row-like patterns found."""
result = _recover_truncated_rows("completely invalid text", 1, "length")
assert result is None
def test_invalid_row_patterns_skipped(self):
"""Invalid JSON within row-like patterns is skipped."""
text = '{"row_id": 1, invalid} {"row_id": 2, "translation": "ok"}'
result = _recover_truncated_rows(text, 1, "length")
assert result is not None
assert len(result["rows"]) == 1
def test_empty_text(self):
"""Empty text returns None."""
result = _recover_truncated_rows("", 1, "length")
assert result is None
def test_no_finish_reason(self):
"""Works without finish_reason."""
text = '{"row_id": 1, "translation": "hola"}'
result = _recover_truncated_rows(text, 1, None)
assert result is not None
class TestParseLLMResponse:
"""parse_llm_response — full LLM response parsing pipeline."""
def test_valid_json_direct(self):
"""Direct valid JSON response."""
response = json.dumps({
"rows": [
{"row_id": 1, "ru": "привет", "detected_source_language": "en"},
{"row_id": 2, "ru": "мир", "detected_source_language": "en"},
]
})
result = parse_llm_response(response, expected_count=2, target_languages=["ru"])
assert len(result) == 2
assert result["1"]["ru"] == "привет"
assert result["2"]["ru"] == "мир"
def test_missing_rows_key(self):
"""No 'rows' key returns empty dict (defaults to empty list)."""
result = parse_llm_response("{}", expected_count=1)
assert result == {}
def test_rows_not_a_list(self):
"""Rows key is not a list raises ValueError."""
with pytest.raises(ValueError, match="rows"):
parse_llm_response('{"rows": "not_a_list"}', expected_count=1)
def test_markdown_code_block(self):
"""Response wrapped in markdown code block."""
text = "```json\n{\"rows\": [{\"row_id\": 1, \"ru\": \"привет\"}]}\n```"
result = parse_llm_response(text, expected_count=1, target_languages=["ru"])
assert len(result) == 1
assert result["1"]["ru"] == "привет"
def test_truncated_recovery(self):
"""Truncated JSON recovers complete rows."""
text = '{"row_id": 1, "ru": "привет"} {"row_id": 2, "ru": "мир"'
result = parse_llm_response(text, expected_count=2, target_languages=["ru"])
assert len(result) >= 1
def test_invalid_json_fallback_to_markdown(self):
"""Invalid JSON falls through to markdown recovery."""
text = "some text\n```json\n{\"rows\": [{\"row_id\": 1, \"ru\": \"hola\"}]}\n```"
result = parse_llm_response(text, expected_count=1, target_languages=["ru"])
assert len(result) == 1
def test_row_without_id_skipped(self):
"""Row without row_id is skipped."""
response = json.dumps({
"rows": [
{"translation": "no_id"},
{"row_id": 2, "ru": "val", "detected_source_language": "en"},
]
})
result = parse_llm_response(response, expected_count=2, target_languages=["ru"])
assert len(result) == 1
assert "2" in result
def test_no_language_data_falls_back_to_translation(self):
"""Row without language key falls back to 'translation' key."""
response = json.dumps({
"rows": [
{"row_id": 1, "translation": "hola", "detected_source_language": "en"},
]
})
result = parse_llm_response(response, expected_count=1, target_languages=["ru"])
assert result["1"]["translation"] == "hola"
def test_no_language_or_translation_skipped(self):
"""Row with neither language nor translation is skipped."""
response = json.dumps({
"rows": [
{"row_id": 1, "detected_source_language": "en"},
]
})
result = parse_llm_response(response, expected_count=1, target_languages=["ru"])
assert len(result) == 0
def test_detected_language_defaults(self):
"""Missing detected_source_language defaults to 'und'."""
response = json.dumps({
"rows": [
{"row_id": 1, "ru": "привет"},
]
})
result = parse_llm_response(response, expected_count=1, target_languages=["ru"])
assert result["1"]["detected_source_language"] == "und"
def test_fully_failed_parse_raises(self):
"""Completely unparseable response raises ValueError."""
with pytest.raises(ValueError, match="not valid JSON"):
parse_llm_response("!@#$%^&*", expected_count=1)
def test_empty_target_languages(self):
"""Empty target_languages uses 'translation' fallback."""
response = json.dumps({
"rows": [
{"row_id": 1, "translation": "hola", "detected_source_language": "en"},
]
})
result = parse_llm_response(response, expected_count=1, target_languages=[])
assert result["1"]["translation"] == "hola"
def test_empty_rows(self):
"""Empty rows array returns empty dict."""
response = json.dumps({"rows": []})
result = parse_llm_response(response, expected_count=1)
assert result == {}

View File

@@ -0,0 +1,94 @@
# #region Test.PreviewResponseParser [C:3] [TYPE Module] [SEMANTICS test, translate, response, parser, data, extract]
# @BRIEF Tests for preview_response_parser.py — extract_data_rows.
# @RELATION BINDS_TO -> [preview_response_parser]
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest
from src.plugins.translate.preview_response_parser import extract_data_rows
class TestExtractDataRows:
"""extract_data_rows — extract data rows from Superset chart data API response."""
def test_result_list_with_data(self):
"""result is a list with item containing data key."""
response = {
"result": [
{"data": [{"col1": "val1"}, {"col1": "val2"}]}
]
}
result = extract_data_rows(response)
assert len(result) == 2
assert result[0]["col1"] == "val1"
def test_result_dict_with_data(self):
"""result is a dict with data key."""
response = {
"result": {"data": [{"col1": "val1"}]}
}
result = extract_data_rows(response)
assert len(result) == 1
def test_fallback_to_response_data(self):
"""Fallback to response.data when result has no data."""
response = {
"data": [{"col1": "val1"}]
}
result = extract_data_rows(response)
assert len(result) == 1
def test_result_list_returned_directly(self):
"""When result is a list with no data key, return it."""
response = {
"result": [{"col1": "val1"}, {"col1": "val2"}]
}
result = extract_data_rows(response)
assert len(result) == 2
def test_empty_result_list(self):
"""Empty result list returns empty list."""
response = {"result": []}
result = extract_data_rows(response)
assert result == []
def test_empty_data_list(self):
"""Empty data list falls back to returning result list."""
response = {
"result": [{"data": []}]
}
result = extract_data_rows(response)
# Returns the result list as fallback
assert result == [{"data": []}]
def test_no_result_or_data_key(self):
"""No result or data key returns empty list."""
response = {"other": "value"}
result = extract_data_rows(response)
assert result == []
def test_result_not_list_or_dict(self):
"""Result is neither list nor dict."""
response = {"result": "string_value"}
result = extract_data_rows(response)
assert result == []
def test_result_list_item_no_data(self):
"""Result list item has no data key, falls back to result list."""
response = {
"result": [{"other": "value"}]
}
result = extract_data_rows(response)
# Returns the result list as fallback
assert result == [{"other": "value"}]
def test_result_dict_data_empty_list(self):
"""Result dict data is empty list."""
response = {
"result": {"data": []}
}
result = extract_data_rows(response)
assert result == []

View File

@@ -0,0 +1,290 @@
# #region Test.TokenBudget [C:3] [TYPE Module] [SEMANTICS test, translate, token, budget, estimation]
# @BRIEF Tests for _token_budget.py — token estimation, batch sizing, output-aware constraints.
# @RELATION BINDS_TO -> [_token_budget]
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest
from src.plugins.translate._token_budget import (
_estimate_tokens_for_text,
_count_rows_that_fit,
_calculate_output_tokens,
_compute_max_rows_by_output,
_apply_output_aware_batch_sizing,
_build_warning,
estimate_token_budget,
)
class TestEstimateTokensForText:
"""_estimate_tokens_for_text — CJK-aware heuristic token counting."""
def test_empty_string(self):
"""Empty text returns 1."""
assert _estimate_tokens_for_text("") == 1
def test_ascii_only(self):
"""Pure ASCII text ~1.8 chars/token."""
result = _estimate_tokens_for_text("hello world")
assert result >= 1
assert isinstance(result, int)
def test_cjk_only(self):
"""Pure CJK text ~1.0 chars/token."""
result = _estimate_tokens_for_text("你好世界")
assert result >= 1
def test_mixed_text(self):
"""Mixed CJK + ASCII."""
result = _estimate_tokens_for_text("hello 你好 world 世界")
assert result >= 1
def test_cjk_ranges(self):
"""CJK includes Unicode ranges. Fullwidth forms."""
result = _estimate_tokens_for_text("\uff01\uff02")
assert result >= 1
def test_long_text(self):
"""Long text estimate is proportional."""
short = _estimate_tokens_for_text("hello")
long_ = _estimate_tokens_for_text("hello " * 100)
assert long_ > short
class TestCountRowsThatFit:
"""_count_rows_that_fit — consecutive rows within budget."""
def test_all_rows_fit(self):
"""All rows fit within budget."""
safe, total = _count_rows_that_fit([10, 20, 30], 5000)
assert safe == 3
assert total == 60
def test_partial_fit(self):
"""Only first 2 rows fit."""
safe, total = _count_rows_that_fit([10, 20, 5000], 2100)
assert safe == 2
assert total == 30
def test_no_rows_fit(self):
"""First row exceeds budget."""
safe, total = _count_rows_that_fit([5000], 1000)
assert safe == 0
assert total == 0
def test_empty_input(self):
"""Empty list returns (0, 0)."""
safe, total = _count_rows_that_fit([], 5000)
assert safe == 0
assert total == 0
def test_single_row_just_fits(self):
"""Single row just fits (with reasoning overhead)."""
budget = 200 + 2001 # row tokens + REASONING_OVERHEAD + 1
safe, total = _count_rows_that_fit([200], budget)
assert safe == 1
assert total == 200
class TestCalculateOutputTokens:
"""_calculate_output_tokens — output budget estimation."""
def test_basic(self):
"""Basic calculation returns positive int."""
result = _calculate_output_tokens(
safe_size=5, num_languages=2
)
assert result >= 1
assert isinstance(result, int)
def test_zero_safe_size(self):
"""Zero safe_size returns only overhead."""
result = _calculate_output_tokens(safe_size=0, num_languages=2)
assert result >= 2000 # REASONING_OVERHEAD
def test_large_batch(self):
"""Large batch produces larger estimate."""
small = _calculate_output_tokens(1, 1)
large = _calculate_output_tokens(50, 5)
assert large > small
class TestComputeMaxRowsByOutput:
"""_compute_max_rows_by_output — output-constrained row limit."""
def test_basic(self):
"""Positive result for normal params."""
result = _compute_max_rows_by_output(max_output_tokens=16384, num_languages=2)
assert result >= 1
def test_small_output_budget(self):
"""Tiny output budget returns 1."""
result = _compute_max_rows_by_output(max_output_tokens=100, num_languages=2)
assert result >= 1
def test_zero_per_row(self):
"""Edge: zero per_row returns fallback 20."""
result = _compute_max_rows_by_output(max_output_tokens=16384, num_languages=0)
assert result >= 1
class TestApplyOutputAwareBatchSizing:
"""_apply_output_aware_batch_sizing — reduce batch until output fits."""
def test_no_reduction_needed(self):
"""Batch fits without reduction."""
result = _apply_output_aware_batch_sizing(
safe_size=10, num_languages=1, max_output_tokens=50000
)
assert result == 10
def test_full_reduction(self):
"""Batch reduced when output exceeds budget."""
result = _apply_output_aware_batch_sizing(
safe_size=10, num_languages=3, max_output_tokens=2000
)
assert result < 10
def test_zero_size(self):
"""Zero safe_size returns 0."""
result = _apply_output_aware_batch_sizing(
safe_size=0, num_languages=1, max_output_tokens=50000
)
assert result == 0
class TestBuildWarning:
"""_build_warning — warning message generation."""
def test_no_warning_when_size_ok(self):
"""No warning when batch size matches safe size."""
assert _build_warning(10, 10, 20, 64000, 1000, 500, None) is None
def test_reduced_batch_warning(self):
"""Warning when safe_size < requested batch_size."""
w = _build_warning(10, 5, 20, 64000, 1000, 500, None)
assert w is not None
assert "Reduced" in w
def test_auto_calc_warning(self):
"""Warning when auto-calculated batch smaller than total rows."""
w = _build_warning(None, 5, 20, 64000, 1000, 500, None)
assert w is not None
assert "Auto-calculated" in w
def test_dict_warning_appended(self):
"""Dictionary warning is appended."""
w = _build_warning(10, 5, 20, 64000, 1000, 500, "Dict entries capped")
assert w is not None
assert "Dict entries capped" in w
def test_dict_warning_only(self):
"""Dictionary warning alone when no batch reduction."""
w = _build_warning(None, 20, 20, 64000, 1000, 500, "Dict capped")
assert w == "Dict capped"
class TestEstimateTokenBudget:
"""estimate_token_budget — main budget estimation entry point."""
def test_empty_source_rows(self):
"""Empty source rows returns sensible defaults."""
result = estimate_token_budget(
source_rows=[],
target_languages=["ru"],
)
assert result["batch_size_adjusted"] >= 1
assert "estimated_input_tokens" in result
assert "estimated_output_tokens" in result
assert "max_output_needed" in result
def test_single_row_single_lang(self):
"""Single row, single language."""
result = estimate_token_budget(
source_rows=[{"source_text": "hello world"}],
target_languages=["ru"],
)
assert result["batch_size_adjusted"] >= 1
def test_multiple_rows(self):
"""Multiple rows produce batch estimate."""
rows = [{"source_text": f"row {i}"} for i in range(5)]
result = estimate_token_budget(
source_rows=rows,
target_languages=["ru", "de"],
max_output_tokens=32000,
)
assert result["batch_size_adjusted"] >= 1
def test_with_context_columns(self):
"""Context columns add to token estimate."""
rows = [{"source_text": "hello", "description": "a long description here"}]
result = estimate_token_budget(
source_rows=rows,
target_languages=["ru"],
source_column="source_text",
context_columns=["description"],
)
assert result["batch_size_adjusted"] >= 1
def test_with_dictionary_entries(self):
"""Dictionary entries add tokens."""
rows = [{"source_text": "hello"}]
result = estimate_token_budget(
source_rows=rows,
target_languages=["ru"],
dictionary_entries=[{"id": 1}, {"id": 2}],
)
assert result["batch_size_adjusted"] >= 1
def test_with_provider_info(self):
"""Provider info resolves context window."""
rows = [{"source_text": "hello"}]
result = estimate_token_budget(
source_rows=rows,
target_languages=["ru"],
provider_info="gpt-4o-mini",
)
assert result["batch_size_adjusted"] >= 1
def test_explicit_context_window(self):
"""Explicit context_window overrides provider default."""
rows = [{"source_text": "hello"}]
result = estimate_token_budget(
source_rows=rows,
target_languages=["ru"],
context_window=128000,
max_output_tokens=4096,
)
assert result["batch_size_adjusted"] >= 1
assert result["available_input_budget"] == 128000 - 4096
def test_large_batch_reduced(self):
"""Batch size reduced when too large."""
many_rows = [{"source_text": "x" * 5000} for _ in range(100)]
result = estimate_token_budget(
source_rows=many_rows,
target_languages=["ru", "de", "fr"],
batch_size=50,
)
assert result["batch_size_adjusted"] >= 1
def test_no_target_languages_defaults(self):
"""Empty target_languages defaults to ['en']."""
result = estimate_token_budget(
source_rows=[{"source_text": "hello"}],
target_languages=[],
)
assert result["batch_size_adjusted"] >= 1
def test_provider_not_found_fallback(self):
"""Unknown provider falls back to defaults."""
result = estimate_token_budget(
source_rows=[{"source_text": "hello"}],
target_languages=["ru"],
provider_info="unknown-model-42",
)
assert result["batch_size_adjusted"] >= 1