mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 15:03:16 +00:00
Applies the plan's C5 step: git mv every test per the domain-reorg
move-map (movemap-m50.txt) into tests/{agent,analyzer,auth,dashboard,
github,middleware,models,reviewer,sandbox,slack,tools,webhooks}/, plus
the 13 fork-only placements from the scoping report §2c (Atlassian
webhook tests -> tests/webhooks/, test_atlassian_connect.py and
test_auth_error_leak.py -> tests/auth/, jira/confluence util tests ->
tests/tools/, test_repo_binding_isolation.py -> tests/sandbox/,
bot-identity/autofix tests -> tests/github/).
Path-only move: the only content edits are parents[1] -> parents[2]
fixes in test_e2b_integration.py and test_daytona_integration.py,
required because their __file__-relative ROOT path gained one more
directory level in the move.
Monkeypatch retargets for these files were already completed in C4;
none remained outstanding here.
76 lines
2.9 KiB
Python
76 lines
2.9 KiB
Python
"""Unit tests for SanitizeToolInputsMiddleware.
|
|
|
|
Guards against the regression where the LLM generates a string value for an
|
|
integer field in read_file (e.g. offset='1, 80'), causing a Pydantic
|
|
ValidationError and an unnecessary retry.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from agent.middleware.sanitize_tool_inputs import _coerce_int, _sanitize_read_file_args
|
|
|
|
|
|
class TestCoerceInt:
|
|
def test_already_int_passes_through(self) -> None:
|
|
assert _coerce_int(1) == 1
|
|
|
|
def test_none_passes_through(self) -> None:
|
|
assert _coerce_int(None) is None
|
|
|
|
def test_extracts_leading_integer_from_comma_string(self) -> None:
|
|
# Production trace 1: offset='1, 80'
|
|
assert _coerce_int("1, 80") == 1
|
|
|
|
def test_extracts_leading_integer_from_embedded_json(self) -> None:
|
|
# Production trace 2: offset='170, "limit": 60'
|
|
assert _coerce_int('170, "limit": 60') == 170
|
|
|
|
def test_extracts_leading_integer_from_trailing_comma(self) -> None:
|
|
# Production trace 3: offset='1504, '
|
|
assert _coerce_int("1504, ") == 1504
|
|
|
|
def test_returns_none_when_no_digits(self) -> None:
|
|
assert _coerce_int("abc") is None
|
|
|
|
def test_handles_leading_whitespace(self) -> None:
|
|
assert _coerce_int(" 42, extra") == 42
|
|
|
|
|
|
class TestSanitizeReadFileArgs:
|
|
def test_coerces_offset_string_to_int(self) -> None:
|
|
args = {"file_path": "foo.ts", "offset": "1, 80", "limit": 80}
|
|
result = _sanitize_read_file_args(args)
|
|
assert result["offset"] == 1
|
|
assert result["limit"] == 80
|
|
assert result["file_path"] == "foo.ts"
|
|
|
|
def test_coerces_offset_with_embedded_json(self) -> None:
|
|
args = {"file_path": "bar.tsx", "offset": '170, "limit": 60', "limit": 60}
|
|
result = _sanitize_read_file_args(args)
|
|
assert result["offset"] == 170
|
|
|
|
def test_coerces_offset_with_trailing_comma(self) -> None:
|
|
args = {"file_path": "baz.go", "offset": "1504, ", "limit": 200}
|
|
result = _sanitize_read_file_args(args)
|
|
assert result["offset"] == 1504
|
|
|
|
def test_int_offset_unchanged(self) -> None:
|
|
args = {"file_path": "foo.ts", "offset": 42, "limit": 80}
|
|
result = _sanitize_read_file_args(args)
|
|
assert result["offset"] == 42
|
|
|
|
def test_missing_offset_unchanged(self) -> None:
|
|
args = {"file_path": "foo.ts"}
|
|
result = _sanitize_read_file_args(args)
|
|
assert "offset" not in result
|
|
|
|
def test_uncoercible_offset_passed_through_unchanged(self) -> None:
|
|
# If no digits at all, we leave the value alone so ToolErrorMiddleware handles it.
|
|
args = {"file_path": "foo.ts", "offset": "bad"}
|
|
result = _sanitize_read_file_args(args)
|
|
assert result["offset"] == "bad"
|
|
|
|
def test_does_not_mutate_original_dict(self) -> None:
|
|
args = {"file_path": "foo.ts", "offset": "1, 80"}
|
|
_ = _sanitize_read_file_args(args)
|
|
assert args["offset"] == "1, 80"
|