agent-float-term 0.3.4

Harness Floating Terminal: a persistent F7 shell for unwrapped AI CLIs
Documentation
"""Exercise installation ordering with fake build tools, never a live install."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest


class InstallLocalTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory(prefix="aft-install-local-")
        self.addCleanup(self.temp.cleanup)
        self.root = Path(self.temp.name)
        self.repo = self.root / "source with spaces"
        for path in ("scripts", "target/release", "bin"):
            (self.repo / path).mkdir(parents=True)
        shutil.copyfile(Path(__file__).with_name("install-local.sh"),
                        self.repo / "scripts/install-local.sh")
        self.log = self.root / "calls"
        self.home = self.root / "home"
        self.home.mkdir()
        self.env = {
            "PATH": str(self.repo / "bin") + os.pathsep + os.defpath,
            "HOME": str(self.home),
            "XDG_DATA_HOME": str(self.home / "custom data"),
            "TEST_LOG": str(self.log),
        }
        self.script("bin/cargo", 'printf "cargo:%s\\n" "$*" >> "$TEST_LOG"\nexit "${FAIL_CARGO:-0}"')
        self.script("target/release/agent-float-term", '''
printf 'app:%s\n' "$*" >> "$TEST_LOG"
if [ "$*" = install ]; then exit "${FAIL_PREVIEW:-0}"; fi
exit "${FAIL_APPLY:-0}"
''')
        self.script("scripts/build-mouse-tmux.sh", '''
printf 'tmux-build\n' >> "$TEST_LOG"
[ "${FAIL_TMUX:-0}" = 0 ] || exit 1
mkdir "$1"
printf '%s/tmux\n' "$1"
''')

    def script(self, name, body):
        path = self.repo / name
        path.write_text("#!/bin/sh\nset -eu\n" + body + "\n")
        path.chmod(0o700)

    def run_install(self, **env):
        return subprocess.run(
            ["bash", str(self.repo / "scripts/install-local.sh")],
            cwd=self.root, env={**self.env, **env}, capture_output=True,
            text=True, timeout=10,
        )

    def calls(self):
        return self.log.read_text().splitlines() if self.log.exists() else []

    def test_dry_run_has_no_build_or_install_side_effects(self):
        result = self.run_install(DRY_RUN="1")
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertIn("Plan only", result.stdout)
        self.assertEqual(self.calls(), [])
        self.assertEqual(list(self.home.iterdir()), [])

    def test_install_orders_apply_after_build_and_preserves_previous_builds(self):
        for _ in range(2):
            result = self.run_install()
            self.assertEqual(result.returncode, 0, result.stderr)
            self.assertIn("AFT_TMUX_BINARY=", result.stdout)
            self.assertIn("Existing tmux servers and sessions were not changed", result.stdout)
            launch = next(line for line in result.stdout.splitlines()
                          if line.startswith("AFT_TMUX_BINARY="))
            syntax = subprocess.run(["bash", "-n"], input=launch, text=True,
                                    capture_output=True, timeout=10)
            self.assertEqual(syntax.returncode, 0, syntax.stderr)
        self.assertEqual(self.calls(), [
            "cargo:+1.84.1 build --locked --release", "app:install",
            "tmux-build", "app:install --yes",
        ] * 2)
        builds = list((Path(self.env["XDG_DATA_HOME"]) / "agent-float-term/compat").iterdir())
        self.assertEqual(len(builds), 2)
        for build in builds:
            self.assertEqual(build.stat().st_mode & 0o777, 0o700)
            self.assertTrue((build / "build").is_dir())

    def test_failures_stop_before_later_mutations(self):
        for failure, expected_count in (("FAIL_CARGO", 1), ("FAIL_PREVIEW", 2),
                                        ("FAIL_TMUX", 3), ("FAIL_APPLY", 4)):
            with self.subTest(failure=failure):
                self.log.unlink(missing_ok=True)
                result = self.run_install(**{failure: "1"})
                self.assertNotEqual(result.returncode, 0)
                self.assertEqual(len(self.calls()), expected_count)
                self.assertNotIn("Installation complete", result.stdout)

    def test_invalid_options_fail_without_building(self):
        for env in ({"DRY_RUN": "yes"}, {"XDG_DATA_HOME": "relative"}):
            with self.subTest(env=env):
                self.assertNotEqual(self.run_install(**env).returncode, 0)
                self.assertEqual(self.calls(), [])


if __name__ == "__main__":
    unittest.main()