# Clean Install Test **Date:** 2026-06-02 **Status:** Draft (pending review) --- ## Context & Motivation The user wants a "clean install" test that verifies Manual Slop works correctly when installed from scratch in an isolated environment. The test should: 1. Clone the repo to a temp directory (no shared state with the source) 2. Install dependencies via `uv sync` 3. Launch `sloppy.py --enable-test-hooks` 4. Verify the Hook API responds (smoke test that the app is functional) This is a defense against: - Repository changes that only work on the developer's machine - Dependency drift (works on dev, fails on fresh install) - Build/launch issues that only appear in clean environments The target is the user's private Gitea server: `https://git.cozyair.dev/ed/manual_slop`. This is intentionally NOT a public GitHub URL — the test must work in the user's private infrastructure. --- ## Scope ### In Scope - `tests/test_clean_install.py` — Opt-in pytest test - `pyproject.toml` update: add `clean_install` marker - Gating via `RUN_CLEAN_INSTALL_TEST=1` env var ### Out of Scope - Auto-clone in CI (the user can opt in via a future CI workflow) - Continuous monitoring - Cloning from a specific branch/tag (uses HEAD of main by default) --- ## Design ### Test File Structure ```python # tests/test_clean_install.py import os import shutil import subprocess import time from pathlib import Path import pytest import requests REPO_URL = "https://git.cozyair.dev/ed/manual_slop" STARTUP_TIMEOUT_SECONDS = 30 READINESS_POLL_INTERVAL = 0.5 @pytest.mark.clean_install def test_clean_install_runs_with_hooks(tmp_path): """Clone the repo, install deps, launch sloppy.py, verify Hook API.""" if os.environ.get("RUN_CLEAN_INSTALL_TEST") != "1": pytest.skip("Set RUN_CLEAN_INSTALL_TEST=1 to enable") clone_dir = tmp_path / "manual_slop" # 1. Clone result = subprocess.run( ["git", "clone", REPO_URL, str(clone_dir)], capture_output=True, text=True, timeout=60, ) assert result.returncode == 0, f"Clone failed: {result.stderr}" # 2. Install deps result = subprocess.run( ["uv", "sync"], cwd=str(clone_dir), capture_output=True, text=True, timeout=180, ) assert result.returncode == 0, f"uv sync failed: {result.stderr}" # 3. Launch sloppy.py with hooks process = subprocess.Popen( ["uv", "run", "sloppy.py", "--enable-test-hooks"], cwd=str(clone_dir), stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0, ) try: # 4. Poll /status endpoint start = time.time() ready = False while time.time() - start < STARTUP_TIMEOUT_SECONDS: if process.poll() is not None: pytest.fail(f"Process exited early. stderr: {process.stderr.read()[:2000]}") try: response = requests.get( "http://127.0.0.1:8999/status", timeout=1.0, ) if response.status_code == 200: payload = response.json() if payload.get("status") == "running": ready = True break except (requests.ConnectionError, requests.Timeout): pass time.sleep(READINESS_POLL_INTERVAL) assert ready, f"Hook server did not respond within {STARTUP_TIMEOUT_SECONDS}s" # 5. Test a write hook (any POST endpoint that should respond) response = requests.get( "http://127.0.0.1:8999/api/mma_status", timeout=5.0, ) assert response.status_code == 200 # The mma_status endpoint returns a dict; verify it has expected keys data = response.json() assert "status" in data or "mma_state" in data finally: # 6. Cleanup if os.name == 'nt': subprocess.run( ["taskkill", "/F", "/T", "/PID", str(process.pid)], capture_output=True, ) else: process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() ``` ### `pyproject.toml` Update ```toml [tool.pytest.ini_options] markers = [ "integration: integration tests requiring live GUI", "strict: tests that require strict mode", "clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)", ] ``` ### Running the Test **Default (skip):** ```bash uv run pytest tests/test_clean_install.py -v # SKIPPED: Set RUN_CLEAN_INSTALL_TEST=1 to enable ``` **Opt-in:** ```bash RUN_CLEAN_INSTALL_TEST=1 uv run pytest tests/test_clean_install.py -v ``` **Just the clean_install marker:** ```bash RUN_CLEAN_INSTALL_TEST=1 uv run pytest -m clean_install -v ``` --- ## File Structure - `tests/test_clean_install.py` — NEW - `pyproject.toml` — MODIFY: add `clean_install` marker --- ## Acceptance Criteria - `RUN_CLEAN_INSTALL_TEST=1 uv run pytest tests/test_clean_install.py -v` passes when run in an environment with network access to `git.cozyair.dev` - Without the env var, the test skips (no network access required) - The test takes 30-90 seconds to run (clone + install + launch) - A failure in any step (clone, sync, launch, hook response) results in a clear error message - Process cleanup is robust (no orphaned processes on Windows or Unix) --- ## Risks 1. **Network dependency:** The test requires network access to `git.cozyair.dev`. In CI environments without that access, the test will fail. Mitigation: the env var gating makes this opt-in. 2. **Clone target is private:** Unlike GitHub, the URL is on a private Gitea server. Test failure on a public CI would leak the existence of the private repo. Mitigation: only run on private infrastructure; the test is opt-in. 3. **Port conflicts:** The test uses port 8999. If another process is using it, the test will fail. Mitigation: the polling loop detects early exit and reports the port-in-use error. 4. **`uv sync` is slow:** On a fresh machine, `uv sync` can take 30-60 seconds. The test budget is 180 seconds which should be sufficient.