Adding file attachment with and stdout redirection.
This commit is contained in:
+110
-1
@@ -2,8 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
|
||||
from shellbound import attach
|
||||
from shellbound import modes
|
||||
from shellbound.personas import (
|
||||
AmbiguousPersona,
|
||||
@@ -231,4 +233,111 @@ def test_renderer_plain_content_streams():
|
||||
renderer = StreamRenderer(plain=True)
|
||||
renderer.add_content("just text")
|
||||
assert renderer.content_parts == ["just text"]
|
||||
assert renderer.finish() == "just text"
|
||||
assert renderer.finish() == "just text"
|
||||
|
||||
|
||||
# ---- attachments / piped stdin --------------------------------------------
|
||||
|
||||
|
||||
def _warnings():
|
||||
seen: list[str] = []
|
||||
|
||||
def warn(message: str) -> None:
|
||||
seen.append(message)
|
||||
|
||||
return seen, warn
|
||||
|
||||
|
||||
def test_fence_structure():
|
||||
assert attach.fence("file: a.txt", "one\ntwo") == (
|
||||
"[file: a.txt]\n```text\none\ntwo\n```"
|
||||
)
|
||||
assert attach.fence("stdin", "x", note="[truncated]") == (
|
||||
"[stdin]\n```text\nx\n[truncated]\n```"
|
||||
)
|
||||
assert attach.fence("clipboard", "\n") == "[clipboard]\n```text\n\n```"
|
||||
|
||||
|
||||
def test_assemble_order_and_stripping():
|
||||
blocks = [
|
||||
attach.fence("stdin", "pipe"),
|
||||
attach.fence("file: a.txt", "file body"),
|
||||
attach.fence("clipboard", "clip"),
|
||||
]
|
||||
assert attach.assemble("hello", blocks) == (
|
||||
"hello\n\n"
|
||||
"[stdin]\n```text\npipe\n```\n\n"
|
||||
"[file: a.txt]\n```text\nfile body\n```\n\n"
|
||||
"[clipboard]\n```text\nclip\n```"
|
||||
)
|
||||
|
||||
|
||||
def test_assemble_no_prompt_or_blocks():
|
||||
assert attach.assemble("", []) == ""
|
||||
assert attach.assemble("just prompt", []) == "just prompt"
|
||||
assert attach.assemble("", [attach.fence("stdin", "s")]) == "[stdin]\n```text\ns\n```"
|
||||
|
||||
|
||||
def test_read_file_binary_skipped(tmp_path):
|
||||
p = tmp_path / "bin.dat"
|
||||
p.write_bytes(b"\x00\x01\x02")
|
||||
seen, warn = _warnings()
|
||||
assert attach.read_file(str(p), 1024, warn=warn) is None
|
||||
assert any("binary" in m for m in seen)
|
||||
|
||||
|
||||
def test_read_file_truncates(tmp_path):
|
||||
p = tmp_path / "big.txt"
|
||||
p.write_text("a" * 5000)
|
||||
seen, warn = _warnings()
|
||||
result = attach.read_file(str(p), 100, warn=warn)
|
||||
assert result.truncated
|
||||
assert result.content == "a" * 100
|
||||
assert not result.binary
|
||||
assert any("truncating" in m for m in seen)
|
||||
|
||||
|
||||
def test_read_file_missing_raises(tmp_path):
|
||||
try:
|
||||
attach.read_file(str(tmp_path / "nope.txt"), 1024)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("expected FileNotFoundError")
|
||||
|
||||
|
||||
def test_read_stdin_truncates_and_warns():
|
||||
seen, warn = _warnings()
|
||||
result = attach.read_stdin(io.BytesIO(b"b" * 500), 10, warn=warn)
|
||||
assert result.truncated and result.content == "b" * 10
|
||||
assert any("truncating" in m for m in seen)
|
||||
|
||||
|
||||
def test_read_stdin_empty_and_binary():
|
||||
result = attach.read_stdin(io.BytesIO(b""), 100)
|
||||
assert not result.content and not result.truncated
|
||||
seen, warn = _warnings()
|
||||
result = attach.read_stdin(io.BytesIO(b"\x00data"), 100, warn=warn)
|
||||
assert result.binary and result.content == "\x00data"
|
||||
assert any("binary" in m for m in seen)
|
||||
|
||||
|
||||
def test_truncation_note():
|
||||
assert attach.truncation_note("stdin", 42) == "[truncated: kept the first 42 bytes of stdin]"
|
||||
|
||||
|
||||
def test_config_attach_max_bytes():
|
||||
from shellbound.config import Config
|
||||
|
||||
assert Config({}).attach_max_bytes == 102_400
|
||||
assert Config({"attach_max_bytes": "oops"}).attach_max_bytes == 102_400
|
||||
assert Config({"attach_max_bytes": 0}).attach_max_bytes == 1
|
||||
assert Config({"attach_max_bytes": 5_000}).attach_max_bytes == 5_000
|
||||
|
||||
|
||||
def test_config_attach_max_bytes_environ(tmp_path, monkeypatch):
|
||||
from shellbound import config as c
|
||||
|
||||
monkeypatch.setattr(c, "CONFIG_FILE", tmp_path / "missing.json")
|
||||
monkeypatch.setenv("SHELLBOUND_ATTACH_MAX_BYTES", "4048")
|
||||
assert c.Config.load().attach_max_bytes == 4048
|
||||
Reference in New Issue
Block a user