"""Tests for @ file reference resolver."""
from __future__ import annotations
from pathlib import Path
import pytest
from clients.terminal.tui.file_refs import FileRefResolver, MAX_FILE_BYTES, MAX_TOTAL_BYTES
@pytest.fixture
def sample_dir(tmp_path: Path) -> Path:
d = tmp_path / "project"
d.mkdir()
(d / "main.py").write_text("def main():\n pass\n", encoding="utf-8")
(d / "readme.md").write_text("# Hello\n", encoding="utf-8")
(d / "sub").mkdir()
(d / "sub" / "util.py").write_text("def util():\n return 1\n", encoding="utf-8")
return d
def test_resolve_single_file(sample_dir: Path) -> None:
resolver = FileRefResolver(sample_dir)
result = resolver.resolve("check @main.py")
assert not result.errors
assert len(result.attachments) == 1
assert result.attachments[0].display_path == "main.py"
assert "def main():" in result.to_message()
assert "```python file: main.py" in result.to_message()
def test_resolve_directory_without_recursive(sample_dir: Path) -> None:
resolver = FileRefResolver(sample_dir)
result = resolver.resolve("look @.")
assert not result.errors
paths = {a.display_path for a in result.attachments}
assert "main.py" in paths
assert "readme.md" in paths
assert "sub/util.py" not in paths
def test_resolve_directory_recursive(sample_dir: Path) -> None:
resolver = FileRefResolver(sample_dir)
result = resolver.resolve("look @./")
paths = {a.display_path for a in result.attachments}
assert "main.py" in paths
assert "sub/util.py" in paths
def test_resolve_missing_file(sample_dir: Path) -> None:
resolver = FileRefResolver(sample_dir)
result = resolver.resolve("check @missing.py")
assert not result.attachments
assert any("not found" in e for e in result.errors)
def test_resolve_glob(sample_dir: Path) -> None:
resolver = FileRefResolver(sample_dir)
result = resolver.resolve("check @**/*.py")
paths = {a.display_path for a in result.attachments}
assert "main.py" in paths
assert "sub/util.py" in paths
assert "readme.md" not in paths
def test_resolve_tilde_expands(tmp_path: Path, monkeypatch) -> None:
home = tmp_path / "home"
home.mkdir()
(home / "note.txt").write_text("hello", encoding="utf-8")
monkeypatch.setenv("HOME", str(home))
resolver = FileRefResolver(base_dir=tmp_path)
result = resolver.resolve("read @~/note.txt")
assert len(result.attachments) == 1
assert result.attachments[0].content == "hello"
def test_no_refs_returns_unchanged() -> None:
resolver = FileRefResolver(Path.cwd())
text = "just a regular message"
result = resolver.resolve(text)
assert result.prompt == text
assert result.is_empty()
def test_size_limit_per_file(sample_dir: Path) -> None:
big = sample_dir / "big.txt"
big.write_bytes(b"x" * (MAX_FILE_BYTES + 100))
resolver = FileRefResolver(sample_dir)
result = resolver.resolve("read @big.txt")
assert len(result.attachments) == 1
assert result.attachments[0].truncated
assert len(result.attachments[0].content) <= MAX_FILE_BYTES + 100
def test_total_size_limit_stops_processing(sample_dir: Path) -> None:
# Each file is exactly half the total limit; a third file should be skipped.
big1 = sample_dir / "big1.txt"
big2 = sample_dir / "big2.txt"
big3 = sample_dir / "big3.txt"
big1.write_bytes(b"x" * MAX_FILE_BYTES)
big2.write_bytes(b"x" * MAX_FILE_BYTES)
big3.write_bytes(b"x" * 100)
resolver = FileRefResolver(sample_dir)
result = resolver.resolve("read @big1.txt @big2.txt @big3.txt")
assert result.total_bytes <= MAX_TOTAL_BYTES
assert len(result.attachments) == 2
assert any("limit reached" in e for e in result.errors)