"""End-to-end test for `flow dotfiles` against the example repo. Gated by FLOW_RUN_E2E=1 because it builds and runs a container image. Tries podman first, falls back to docker. Skips if neither is available. """ from __future__ import annotations import os import shutil import subprocess from pathlib import Path import textwrap import pytest REPO_ROOT = Path(__file__).resolve().parents[2] CONTAINERFILE = Path(__file__).parent / "Containerfile" EXAMPLE_DIR = REPO_ROOT / "example" IMAGE_TAG = "flow-e2e:test" def _pick_runtime() -> str | None: for binary in ("podman", "docker"): if not shutil.which(binary): continue result = subprocess.run( [binary, "version"], capture_output=True, text=True, ) if result.returncode == 0: return binary return None def _flow_script(*commands: str) -> str: """Build a reusable flow sandbox script for container execution.""" return textwrap.dedent( """ set -euo pipefail export XDG_CONFIG_HOME="/tmp/flow-config" export XDG_DATA_HOME="/tmp/flow-data" export XDG_STATE_HOME="/tmp/flow-state" export TARGET_HOSTNAME="flow-e2e" export USER_EMAIL="e2e@example.com" export PYTHONPATH="/opt/flow-src/src:${PYTHONPATH:-}" rm -rf "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" mkdir -p "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" cp -r /example/dotfiles-repo "$HOME/dotfiles-src" cp -r /example/module-repos "$HOME/module-repos" cd "$HOME/module-repos/nvim-config" git init -q -b main git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" add -A git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" commit -q -m initial cd "$HOME/dotfiles-src" git init -q -b main git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" add -A git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" commit -q -m initial cd "$HOME" """ ).strip() + "\n" + "\n".join(commands) + "\n" def _run_in_e2e_container(runtime: str, script: str) -> subprocess.CompletedProcess[str]: return subprocess.run( [ runtime, "run", "--rm", "-v", f"{EXAMPLE_DIR}:/example:ro", IMAGE_TAG, "-c", script, ], capture_output=True, text=True, ) @pytest.fixture(scope="session") def runtime() -> str: if os.environ.get("FLOW_RUN_E2E") != "1": pytest.skip("set FLOW_RUN_E2E=1 to run") selected_runtime = _pick_runtime() if selected_runtime is None: pytest.skip("neither podman nor docker is available") return selected_runtime @pytest.fixture(scope="session") def image(runtime: str): build = subprocess.run( [ runtime, "build", "-f", str(CONTAINERFILE), "-t", IMAGE_TAG, str(REPO_ROOT), ], capture_output=True, text=True, ) if build.returncode != 0: pytest.fail(f"image build failed:\n{build.stdout}\n{build.stderr}") try: yield IMAGE_TAG finally: subprocess.run( [runtime, "rmi", "-f", IMAGE_TAG], capture_output=True, text=True, ) @pytest.mark.skipif( os.environ.get("FLOW_RUN_E2E") != "1", reason="set FLOW_RUN_E2E=1 to run", ) def test_dotfiles_init_and_link_in_container(runtime: str, image: str): result = _run_in_e2e_container( runtime, _flow_script( "flow dotfiles init --repo \"$HOME/dotfiles-src\"", "flow dotfiles link --profile linux-auto --skip _root", "test -L \"$HOME/.zshrc\"", "test -L \"$HOME/.gitconfig\"", "readlink \"$HOME/.zshrc\" | grep -q '/dotfiles/_shared/zsh/.zshrc'", "readlink \"$HOME/.gitconfig\" | grep -q '/dotfiles/_shared/git/.gitconfig'", "flow dotfiles link --profile linux-auto --skip _root", "flow dotfiles status", ), ) assert result.returncode == 0, ( f"e2e run failed (rc={result.returncode}):\n" f"stdout: {result.stdout}\n" f"stderr: {result.stderr}" ) @pytest.mark.skipif( os.environ.get("FLOW_RUN_E2E") != "1", reason="set FLOW_RUN_E2E=1 to run", ) def test_cli_paths_run_in_disposable_container(runtime: str, image: str): result = _run_in_e2e_container( runtime, _flow_script( "flow --version | tee /tmp/version.txt", "grep -q \"flow\" /tmp/version.txt", "flow --help | tee /tmp/help.txt", "test -s /tmp/help.txt", "flow completion zsh | tee /tmp/completion.zsh", "test -s /tmp/completion.zsh", "flow dotfiles init --repo \"$HOME/dotfiles-src\"", "flow dotfiles link --profile linux-auto --skip _root", "flow dotfiles status", "flow packages list --all | tee /tmp/packages.txt", "test -s /tmp/packages.txt", "flow packages install --profile linux-auto --dry-run | tee /tmp/package-dry-run.txt", "test -s /tmp/package-dry-run.txt", "flow setup show linux-auto | tee /tmp/setup.txt", "test -s /tmp/setup.txt", ), ) assert result.returncode == 0, ( f"e2e run failed (rc={result.returncode}):\n" f"stdout: {result.stdout}\n" f"stderr: {result.stderr}" )