mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 13:00:37 +00:00
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
158 lines
5.2 KiB
Python
158 lines
5.2 KiB
Python
"""send_file (§34 / UX-016): deliverables into the chat, with its own approval surface.
|
|
|
|
Gates: happy path via a fake FileSender (no network) · path containment against
|
|
workspace/roots · unsupported platform · missing token · as_screenshot HTML-only with an
|
|
injected renderer · and the permission split — a thread's standing send_message grant must
|
|
NEVER cover send_file.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
from coworker.connectors.base import SendResult
|
|
from coworker.connectors.tools import make_send_file_tool
|
|
from coworker.permissions import Mode, PermissionEngine
|
|
from coworker.roots import RootDir
|
|
from coworker.secrets import SecretStore
|
|
|
|
|
|
def _secrets(tmp_path, token="xoxb-1") -> SecretStore:
|
|
s = SecretStore(tmp_path / "secrets.json")
|
|
if token:
|
|
s.put("slack:default", {"bot_token": token, "enabled": True})
|
|
return s
|
|
|
|
|
|
def _fake_sender(record: list):
|
|
def sender(token, chat_id, thread_id, filename, data, title, comment):
|
|
record.append(
|
|
{
|
|
"token": token,
|
|
"chat_id": chat_id,
|
|
"thread_id": thread_id,
|
|
"filename": filename,
|
|
"data": data,
|
|
"title": title,
|
|
"comment": comment,
|
|
}
|
|
)
|
|
return SendResult(True, message_id="F123")
|
|
|
|
return {"slack": sender}
|
|
|
|
|
|
def test_send_file_success_within_workspace(tmp_path):
|
|
ws = tmp_path / "ws"
|
|
ws.mkdir()
|
|
(ws / "report.pdf").write_bytes(b"%PDF-fake")
|
|
record: list = []
|
|
tool = make_send_file_tool(
|
|
_secrets(tmp_path), workspace=ws, file_senders=_fake_sender(record)
|
|
)
|
|
|
|
out = tool("slack:C9:1700.1", "report.pdf", comment="here you go")
|
|
assert out == {
|
|
"ok": True,
|
|
"file_id": "F123",
|
|
"target": "slack:C9:1700.1",
|
|
"filename": "report.pdf",
|
|
}
|
|
sent = record[0]
|
|
assert sent["chat_id"] == "C9" and sent["thread_id"] == "1700.1"
|
|
assert sent["data"] == b"%PDF-fake" and sent["comment"] == "here you go"
|
|
|
|
|
|
def test_send_file_rejects_paths_outside_roots(tmp_path):
|
|
ws = tmp_path / "ws"
|
|
ws.mkdir()
|
|
outside = tmp_path / "elsewhere.txt"
|
|
outside.write_text("secret")
|
|
tool = make_send_file_tool(
|
|
_secrets(tmp_path), workspace=ws, file_senders=_fake_sender([])
|
|
)
|
|
|
|
# Absolute path outside every base, and a traversal attempt — both refused.
|
|
assert "error" in tool("slack:C9", str(outside))
|
|
assert "error" in tool("slack:C9", "../elsewhere.txt")
|
|
|
|
|
|
def test_send_file_roots_extend_the_reachable_set(tmp_path):
|
|
ws = tmp_path / "ws"
|
|
ws.mkdir()
|
|
shared = tmp_path / "shared"
|
|
shared.mkdir()
|
|
(shared / "data.csv").write_text("a,b\n1,2\n")
|
|
record: list = []
|
|
tool = make_send_file_tool(
|
|
_secrets(tmp_path),
|
|
workspace=ws,
|
|
roots=[RootDir(path=shared)],
|
|
file_senders=_fake_sender(record),
|
|
)
|
|
out = tool("slack:C9", str(shared / "data.csv"))
|
|
assert out["ok"] and record[0]["filename"] == "data.csv"
|
|
|
|
|
|
def test_send_file_unsupported_platform_and_missing_token(tmp_path):
|
|
ws = tmp_path / "ws"
|
|
ws.mkdir()
|
|
(ws / "a.txt").write_text("x")
|
|
tool = make_send_file_tool(
|
|
_secrets(tmp_path), workspace=ws, file_senders=_fake_sender([])
|
|
)
|
|
assert "not supported" in tool("telegram:123", "a.txt")["error"]
|
|
|
|
no_token = make_send_file_tool(
|
|
_secrets(tmp_path / "nt", token=None),
|
|
workspace=ws,
|
|
file_senders=_fake_sender([]),
|
|
)
|
|
assert "no bot token" in no_token("slack:C9", "a.txt")["error"]
|
|
|
|
|
|
def test_send_file_screenshot_is_html_only_and_renames_to_png(tmp_path):
|
|
ws = tmp_path / "ws"
|
|
ws.mkdir()
|
|
(ws / "dash.html").write_text("<h1>hi</h1>")
|
|
(ws / "notes.md").write_text("# hi")
|
|
record: list = []
|
|
tool = make_send_file_tool(
|
|
_secrets(tmp_path),
|
|
workspace=ws,
|
|
file_senders=_fake_sender(record),
|
|
render_html=lambda p: b"PNG-bytes-for-" + Path(p).name.encode(),
|
|
)
|
|
|
|
assert (
|
|
"only applies to .html"
|
|
in tool("slack:C9", "notes.md", as_screenshot=True)["error"]
|
|
)
|
|
|
|
out = tool("slack:C9", "dash.html", as_screenshot=True)
|
|
assert out["ok"] and out["filename"] == "dash.png"
|
|
assert record[-1]["data"] == b"PNG-bytes-for-dash.html"
|
|
|
|
|
|
def test_thread_send_message_grant_never_covers_send_file(tmp_path):
|
|
"""The §31 mention-thread grant pre-approves send_message for its thread target — the
|
|
SAME target on send_file must still ask (task_rules key on the tool name)."""
|
|
engine = PermissionEngine(workspace_root=tmp_path, mode=Mode.INTERACTIVE)
|
|
target = "slack:T1/C9:1700.1"
|
|
engine.task_rules.setdefault("send_message", set()).add(target)
|
|
|
|
from coworker.connectors.tools import make_send_file_tool, make_send_message_tool
|
|
|
|
msg_meta = make_send_message_tool(_secrets(tmp_path)).__aisuite_tool_metadata__
|
|
file_meta = make_send_file_tool(
|
|
_secrets(tmp_path), workspace=tmp_path
|
|
).__aisuite_tool_metadata__
|
|
|
|
allowed = engine.evaluate(
|
|
"send_message", {"target": target, "text": "hi"}, msg_meta
|
|
)
|
|
assert allowed.allowed and "standing rule" in allowed.reason
|
|
|
|
asked = engine.evaluate(
|
|
"send_file", {"target": target, "path": "report.pdf"}, file_meta
|
|
)
|
|
assert not asked.allowed and asked.needs_user
|