diff options
Diffstat (limited to 'claude-templates/.ai/scripts/tests/test_inbox_send.py')
| -rw-r--r-- | claude-templates/.ai/scripts/tests/test_inbox_send.py | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/claude-templates/.ai/scripts/tests/test_inbox_send.py b/claude-templates/.ai/scripts/tests/test_inbox_send.py index f75d7a1..9b0a8c6 100644 --- a/claude-templates/.ai/scripts/tests/test_inbox_send.py +++ b/claude-templates/.ai/scripts/tests/test_inbox_send.py @@ -476,3 +476,117 @@ class TestFilenameCollisions: assert len(files) == 2 bodies = "".join(f.read_text() for f in files) assert "message one" in bodies and "message two" in bodies + + +class TestAtomicWrite: + """A send wrote straight to the destination path in another project's + inbox/, and write_text truncates on open, so any mid-write failure left a + zero-byte .org there. inbox-status counts that phantom as a pending + handoff, blocking a turn in the receiving project over a file with no + content (2026-07-23). The write must be atomic: the inbox sees a complete + file or nothing.""" + + def test_send_text_writes_utf8(self, tmp_path): + from datetime import datetime + mod = _load_module() + inbox = tmp_path / "inbox" + inbox.mkdir() + now = datetime(2026, 7, 23, 4, 36, 0) + # An em dash and an accented char — both non-ASCII. + dest = mod.send_text(inbox, "accent café and dash — here", "src", None, now) + # Reading as utf-8 must round-trip; a locale-encoded write would raise + # under a C locale, and reading back proves the bytes are utf-8. + assert "—" in dest.read_text(encoding="utf-8") + + def test_send_text_no_partial_on_write_failure(self, tmp_path, monkeypatch): + from datetime import datetime + mod = _load_module() + inbox = tmp_path / "inbox" + inbox.mkdir() + now = datetime(2026, 7, 23, 4, 36, 0) + # Force the atomic finalize to fail after the temp file is written. + def boom(*a, **k): + raise OSError("disk full") + monkeypatch.setattr(mod.os, "replace", boom) + with pytest.raises(OSError): + mod.send_text(inbox, "a message that should never half-land", "src", None, now) + # No phantom, no leftover temp: the inbox is empty. + assert list(inbox.iterdir()) == [] + + def test_send_text_leaves_no_temp_on_success(self, tmp_path): + from datetime import datetime + mod = _load_module() + inbox = tmp_path / "inbox" + inbox.mkdir() + now = datetime(2026, 7, 23, 4, 36, 0) + dest = mod.send_text(inbox, "clean send", "src", None, now) + assert list(inbox.iterdir()) == [dest] + + def test_send_file_no_partial_on_write_failure(self, tmp_path, monkeypatch): + from datetime import datetime + mod = _load_module() + inbox = tmp_path / "inbox" + inbox.mkdir() + src = tmp_path / "note.org" + src.write_text("body") + now = datetime(2026, 7, 23, 4, 36, 0) + def boom(*a, **k): + raise OSError("disk full") + monkeypatch.setattr(mod.os, "replace", boom) + with pytest.raises(OSError): + mod.send_file(inbox, src, "src", None, now) + assert list(inbox.iterdir()) == [] + + def test_send_file_leaves_no_temp_on_success(self, tmp_path): + from datetime import datetime + mod = _load_module() + inbox = tmp_path / "inbox" + inbox.mkdir() + src = tmp_path / "note.org" + src.write_text("payload") + now = datetime(2026, 7, 23, 4, 36, 0) + dest = mod.send_file(inbox, src, "src", None, now) + assert list(inbox.iterdir()) == [dest] + assert dest.read_text() == "payload" + + +class TestSmallerDefects: + """Two low-severity defects found reading inbox-send during the 2026-07-23 + sweep: an unreadable source raised an uncaught traceback instead of the + clean error every other failure path produces, and a roots config naming + both a parent and one of its children listed the same project twice.""" + + def test_unreadable_source_gives_clean_error_not_traceback( + self, project_root, run_script, tmp_path + ): + project_root("sender") + project_root("receiver") + roots = [tmp_path / "projects"] + src = tmp_path / "secret.bin" + src.write_text("x") + src.chmod(0o000) + try: + result = run_script( + ["receiver", "--file", str(src)], + cwd=tmp_path / "projects" / "sender", + roots=roots, + expect_failure=True, + ) + finally: + src.chmod(0o644) + assert result.returncode == 1 + # The clean "inbox-send: <message>" shape, not a Python traceback. + assert result.stderr.startswith("inbox-send:") + assert "Traceback" not in result.stderr + + def test_discover_projects_dedupes_parent_and_child_root(self, tmp_path): + mod = _load_module() + # A project directory, reachable both as a child of its parent root and + # as a root in its own right. + parent = tmp_path / "projects" + proj = parent / "app" + (proj / ".ai").mkdir(parents=True) + (proj / "inbox").mkdir() + found = mod.discover_projects([parent, proj]) + resolved = [p.resolve() for p in found] + assert resolved.count(proj.resolve()) == 1 |
