"""Tests for inbox-send.py — universal cross-project inbox messaging tool. The script: - discovers .ai projects with an inbox/ subdirectory under known roots, - writes a text message as a dated .org file in the target's inbox/, or - copies a file into the target's inbox/ with a dated, source-tagged name. All discovery is roots-driven (env var INBOX_SEND_ROOTS overrides the defaults) so tests can sandbox everything inside tmp_path. """ import subprocess from pathlib import Path import pytest SCRIPT = Path(__file__).parent.parent / "inbox-send.py" @pytest.fixture def project_root(tmp_path): """Build a fake project under tmp_path/projects// with .ai/ + top-level inbox/.""" def _make(name: str, has_inbox: bool = True) -> Path: proj = tmp_path / "projects" / name proj.mkdir(parents=True, exist_ok=True) (proj / ".ai").mkdir(exist_ok=True) if has_inbox: (proj / "inbox").mkdir(exist_ok=True) return proj return _make @pytest.fixture def run_script(tmp_path): """Invoke inbox-send with sandboxed roots via INBOX_SEND_ROOTS env var.""" def _run(args, cwd=None, roots=None, expect_failure=False): env = {} # Preserve PATH and a few essentials for python3 to launch. import os as _os env["PATH"] = _os.environ.get("PATH", "") env["HOME"] = _os.environ.get("HOME", "/tmp") if roots: env["INBOX_SEND_ROOTS"] = ":".join(str(r) for r in roots) cmd = ["python3", str(SCRIPT)] + args result = subprocess.run( cmd, capture_output=True, text=True, cwd=cwd or tmp_path, env=env, check=not expect_failure, ) return result return _run # ---------------------------------------------------------------------- # Discovery (--list) # ---------------------------------------------------------------------- class TestInboxSendDiscovery: """Discovering available .ai projects under the configured roots.""" def test_inbox_send_list_detects_projects_with_ai_inbox(self, project_root, run_script, tmp_path): """Normal: --list shows projects that have .ai/inbox/.""" project_root("foo") project_root("bar") result = run_script(["--list"], roots=[tmp_path / "projects"]) assert "foo" in result.stdout assert "bar" in result.stdout def test_inbox_send_list_skips_projects_without_inbox(self, project_root, run_script, tmp_path): """Boundary: project with .ai/ but no inbox/ is not surfaced.""" project_root("withinbox", has_inbox=True) project_root("noinbox", has_inbox=False) result = run_script(["--list"], roots=[tmp_path / "projects"]) assert "withinbox" in result.stdout assert "noinbox" not in result.stdout def test_inbox_send_list_skips_current_project(self, project_root, run_script, tmp_path): """Normal: --list excludes the project the user is currently in.""" cwd_project = project_root("current") project_root("other") result = run_script(["--list"], cwd=cwd_project, roots=[tmp_path / "projects"]) assert "other" in result.stdout assert "current" not in result.stdout def test_inbox_send_list_empty_when_no_projects(self, run_script, tmp_path): """Boundary: no projects under roots → friendly informational message.""" (tmp_path / "projects").mkdir() result = run_script(["--list"], roots=[tmp_path / "projects"]) assert result.returncode == 0 assert "No projects" in result.stdout def test_inbox_send_list_handles_missing_root(self, run_script, tmp_path): """Boundary: configured root doesn't exist → skip silently.""" result = run_script(["--list"], roots=[tmp_path / "does-not-exist"]) assert result.returncode == 0 # ---------------------------------------------------------------------- # Slug derivation from text and from filenames # ---------------------------------------------------------------------- def _slug_from(inbox_files, source_name): """Helper: extract the slug from a deposited file's basename.""" assert len(inbox_files) == 1 name = inbox_files[0].stem marker = f"from-{source_name}-" return name.split(marker, 1)[1] class TestInboxSendNaming: """Slug derivation from --text (and override via --name).""" def test_inbox_send_text_slug_hyphenated_lowercase(self, project_root, run_script, tmp_path): """Normal: 'ATM cash reminder' → slug 'atm-cash-reminder'.""" project_root("target") cwd = project_root("source") run_script( ["target", "--text", "ATM cash reminder"], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert _slug_from(files, "source") == "atm-cash-reminder" def test_inbox_send_text_slug_truncated_at_word_boundary(self, project_root, run_script, tmp_path): """Normal: long text truncated under 40 chars at the nearest word boundary.""" project_root("target") cwd = project_root("source") long_text = ( "Please review the SOFWeek prep doc and confirm the AirBnB kitchen details" ) run_script( ["target", "--text", long_text], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) slug = _slug_from(files, "source") assert slug.startswith("please-review-the-sofweek") assert len(slug) <= 40 # Truncation should land on a word boundary (last char is a letter/digit, not mid-word). assert "-" not in slug[-1] def test_inbox_send_text_slug_strips_punctuation(self, project_root, run_script, tmp_path): """Normal: punctuation stripped, lowercased.""" project_root("target") cwd = project_root("source") run_script( ["target", "--text", "Hey! What's the plan? See you @ 5PM."], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) slug = _slug_from(files, "source") for ch in "!?'@.": assert ch not in slug assert slug == slug.lower() def test_inbox_send_name_override_overrides_slug(self, project_root, run_script, tmp_path): """Normal: --name wins over derived slug.""" project_root("target") cwd = project_root("source") run_script( ["target", "--text", "ok", "--name", "pre-call-ack"], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert _slug_from(files, "source") == "pre-call-ack" # ---------------------------------------------------------------------- # --text mode end-to-end # ---------------------------------------------------------------------- class TestInboxSendText: """--text mode writes a .org file with the message body.""" def test_inbox_send_text_writes_org_file_with_message(self, project_root, run_script, tmp_path): """Normal: produces a .org file whose body contains the message.""" project_root("target") cwd = project_root("source") run_script( ["target", "--text", "Remember the ATM run"], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert len(files) == 1 assert files[0].suffix == ".org" body = files[0].read_text() assert "Remember the ATM run" in body def test_inbox_send_text_filename_includes_source_project_name(self, project_root, run_script, tmp_path): """Normal: filename includes 'from--' so the target knows where it came from.""" project_root("target") cwd = project_root("emacs") run_script( ["target", "--text", "hello"], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert "from-emacs-" in files[0].name # ---------------------------------------------------------------------- # --file mode end-to-end # ---------------------------------------------------------------------- class TestInboxSendFile: """--file mode copies the source file into the target inbox.""" def test_inbox_send_file_copies_text_file(self, project_root, run_script, tmp_path): """Normal: copies a text file to the target inbox, preserving content.""" project_root("target") cwd = project_root("source") src = tmp_path / "doc.org" src.write_text("file content") run_script( ["target", "--file", str(src)], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert len(files) == 1 assert files[0].read_text() == "file content" def test_inbox_send_file_preserves_extension(self, project_root, run_script, tmp_path): """Normal: extension carried from source file.""" project_root("target") cwd = project_root("source") src = tmp_path / "image.png" src.write_bytes(b"\x89PNG\r\n...") run_script( ["target", "--file", str(src)], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert files[0].suffix == ".png" def test_inbox_send_file_slug_from_source_basename(self, project_root, run_script, tmp_path): """Normal: filename slug derived from the source file's basename when --name omitted.""" project_root("target") cwd = project_root("source") src = tmp_path / "branching-strategy-notes.md" src.write_text("notes") run_script( ["target", "--file", str(src)], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert "branching-strategy-notes" in files[0].name def test_inbox_send_file_preserves_dots_in_basename(self, project_root, run_script, tmp_path): """Boundary: a dotted stem keeps its dots — the engine.plugin.org plugin namespace must survive transit.""" project_root("target") cwd = project_root("source") src = tmp_path / "triage-intake.personal-gmail.org" src.write_text("plugin") run_script( ["target", "--file", str(src)], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert len(files) == 1 assert _slug_from(files, "source") == "triage-intake.personal-gmail" assert files[0].suffix == ".org" def test_inbox_send_file_spaces_become_hyphens(self, project_root, run_script, tmp_path): """Normal: whitespace in a filename stem still collapses to hyphens; dots are what's preserved, not spaces.""" project_root("target") cwd = project_root("source") src = tmp_path / "meeting notes draft.org" src.write_text("x") run_script( ["target", "--file", str(src)], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert _slug_from(files, "source") == "meeting-notes-draft" def test_inbox_send_file_name_override(self, project_root, run_script, tmp_path): """Normal: --name overrides the basename-derived slug; extension preserved.""" project_root("target") cwd = project_root("source") src = tmp_path / "random.pdf" src.write_bytes(b"%PDF-1.4...") run_script( ["target", "--file", str(src), "--name", "branching-strategy"], cwd=cwd, roots=[tmp_path / "projects"], ) files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert "branching-strategy" in files[0].name assert files[0].suffix == ".pdf" # ---------------------------------------------------------------------- # Errors and refusal cases # ---------------------------------------------------------------------- class TestInboxSendErrors: """Refusal cases — surface clearly, exit non-zero, leave filesystem untouched.""" def test_inbox_send_refuses_unknown_target(self, project_root, run_script, tmp_path): """Error: target project not found in discovery → refuse.""" cwd = project_root("source") result = run_script( ["nonexistent", "--text", "hi"], cwd=cwd, roots=[tmp_path / "projects"], expect_failure=True, ) assert result.returncode != 0 def test_inbox_send_refuses_no_text_and_no_file(self, project_root, run_script, tmp_path): """Error: must provide one of --text / --file.""" project_root("target") cwd = project_root("source") result = run_script( ["target"], cwd=cwd, roots=[tmp_path / "projects"], expect_failure=True, ) assert result.returncode != 0 def test_inbox_send_refuses_both_text_and_file(self, project_root, run_script, tmp_path): """Error: --text and --file are mutually exclusive.""" project_root("target") cwd = project_root("source") src = tmp_path / "doc.org" src.write_text("x") result = run_script( ["target", "--text", "hi", "--file", str(src)], cwd=cwd, roots=[tmp_path / "projects"], expect_failure=True, ) assert result.returncode != 0 def test_inbox_send_refuses_missing_source_file(self, project_root, run_script, tmp_path): """Error: --file path doesn't exist → refuse.""" project_root("target") cwd = project_root("source") result = run_script( ["target", "--file", str(tmp_path / "definitely-missing.org")], cwd=cwd, roots=[tmp_path / "projects"], expect_failure=True, ) assert result.returncode != 0 def test_inbox_send_refuses_empty_text(self, project_root, run_script, tmp_path): """Error: empty --text refused; nothing written to target inbox.""" project_root("target") cwd = project_root("source") result = run_script( ["target", "--text", " "], cwd=cwd, roots=[tmp_path / "projects"], expect_failure=True, ) assert result.returncode != 0 files = list((tmp_path / "projects" / "target" / "inbox").iterdir()) assert files == []