"""Tests for Dockerfile npm install line. Covers issue #98 acceptance criteria: - ``Dockerfile`` no longer installs ``@mathew-cf/opencode-memory`` — the plugin was replaced by 5 TS tools (``.opencode/tools/memory-*.ts``) that call ``python3 -m src.memory`` directly (PR #101). The npm package is dead weight in the image and is removed from the global install step. - ``opencode-ai`` and ``repomix`` remain (regression guard — must not be dropped when editing the install line). Checks are raw-text grep assertions (no Docker build needed), mirroring the belt-and-suspenders style of ``test_docker_compose.py``. """ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent DOCKERFILE = REPO_ROOT / "Dockerfile" REMOVED_PLUGIN = "@mathew-cf/opencode-memory" KEPT_PACKAGES = ("opencode-ai", "repomix") OPENCODE_AI = "opencode-ai" def _dockerfile_text() -> str: """Read Dockerfile text (asserts the file exists).""" assert DOCKERFILE.exists(), f"Dockerfile missing: {DOCKERFILE}" return DOCKERFILE.read_text() def _npm_install_lines(text: str) -> list[str]: """Return every ``npm install -g ...`` line from the Dockerfile.""" return [line for line in text.splitlines() if "npm install -g" in line] # ── file exists ───────────────────────────────────────────────────────────── def test_dockerfile_exists(): """Dockerfile exists at repo root.""" assert DOCKERFILE.exists(), f"Dockerfile missing: {DOCKERFILE}" # ── @mathew-cf/opencode-memory removed (issue #98 — plugin replaced by TS tools) def test_memory_plugin_removed_from_npm_install(): """Dockerfile does NOT install ``@mathew-cf/opencode-memory`` via ``npm install -g``. The plugin was replaced by 5 TS tools (PR #101) that call ``python3 -m src.memory`` directly — the npm package is no longer needed inside the container and is removed from the global install step. """ lines = _npm_install_lines(_dockerfile_text()) assert lines, "no `npm install -g` line found in Dockerfile" assert not any(REMOVED_PLUGIN in line for line in lines), ( f"Dockerfile must NOT install {REMOVED_PLUGIN!r} via `npm install -g` — " "plugin replaced by TS tools (PR #101, cleanup issue #98)" ) def test_memory_plugin_removed_from_npm_install_raw(): """Raw text check: the Dockerfile does NOT contain ``@mathew-cf/opencode-memory``. Belt-and-suspenders alongside the parsed check — catches any lingering reference to the removed plugin (multi-line install, comment, etc.). """ assert REMOVED_PLUGIN not in _dockerfile_text(), ( f"Dockerfile must NOT contain {REMOVED_PLUGIN!r} — plugin replaced by " "TS tools (PR #101, cleanup issue #98)" ) # ── opencode-ai still present (regression — must not drop the main package) ── def test_opencode_ai_present(): """Dockerfile still installs ``opencode-ai`` (regression guard). Removing the memory plugin from the npm install line must not accidentally drop the primary ``opencode-ai`` package. """ lines = _npm_install_lines(_dockerfile_text()) assert lines, "no `npm install -g` line found in Dockerfile" assert any(OPENCODE_AI in line for line in lines), ( f"Dockerfile must still install {OPENCODE_AI!r} — dropping it breaks " "the container entrypoint" ) def test_opencode_ai_present_raw(): """Raw text check: the Dockerfile contains ``opencode-ai``.""" assert OPENCODE_AI in _dockerfile_text(), f"Dockerfile must still contain {OPENCODE_AI!r}" # ── single npm install line holds the kept packages ────────────────────────── def test_single_npm_install_line_has_kept_packages(): """One ``npm install -g`` line installs all kept packages. Guards against splitting the install across multiple RUN layers (which would waste image layers and obscure which package is missing). """ lines = _npm_install_lines(_dockerfile_text()) install_lines = [line for line in lines if OPENCODE_AI in line] assert install_lines, f"no `npm install -g` line with {OPENCODE_AI!r}" main_line = install_lines[0] for pkg in KEPT_PACKAGES: assert pkg in main_line, ( f"package {pkg!r} missing from the opencode-ai npm install line: {main_line!r}" ) # ── docker CLI install (issue #222 — docker-ce-cli + compose plugin) ───────── def test_docker_io_removed_from_apt_install(): """Dockerfile does NOT install the ``docker.io`` Debian metapackage. The ``docker.io`` package on trixie ships ``dockerd`` + ``docker-proxy`` + ``docker-init`` but NOT the ``docker`` CLI binary. The opencode container delegates to the external DinD service, so running a local daemon is dead weight — only the CLI is needed. ``docker-ce-cli`` (official Docker APT repo) provides ``/usr/bin/docker`` and matches DinD 29.6.2 (API 1.55). """ content = _dockerfile_text() assert "docker.io" not in content, ( "Dockerfile must NOT install `docker.io` — trixie ships no `docker` " "CLI binary, only the daemon; install `docker-ce-cli` instead (issue #222)" ) def test_docker_ce_cli_installed(): """Dockerfile installs ``docker-ce-cli`` from the official Docker APT repo. Provides ``/usr/bin/docker`` and matches DinD 29.6.2 (API 1.55). The Debian ``docker.io`` package does NOT ship the CLI on trixie. """ content = _dockerfile_text() assert "docker-ce-cli" in content, ( "Dockerfile must install `docker-ce-cli` so `which docker` resolves to " "/usr/bin/docker and `docker version` reaches the DinD server (issue #222)" ) def test_docker_compose_plugin_installed(): """Dockerfile installs ``docker-compose-plugin`` (Compose v2 subcommand). The old Python ``docker-compose`` (v1) is NOT compatible. ``docker compose`` (subcommand) requires the plugin, bundled with the official Docker APT repo alongside ``docker-ce-cli``. """ content = _dockerfile_text() assert "docker-compose-plugin" in content, ( "Dockerfile must install `docker-compose-plugin` so `docker compose " "version` prints v2.x (issue #222)" ) def test_docker_apt_repo_added(): """Dockerfile adds the official Docker APT repository (``download.docker.com``). ``docker-ce-cli`` / ``docker-compose-plugin`` are not in the Debian trixie default repos — the official Docker repo is required to match the DinD version (29.6.2, API 1.55). """ content = _dockerfile_text() assert "download.docker.com/linux/debian" in content, ( "Dockerfile must add the official Docker APT repo (download.docker.com) " "to install docker-ce-cli matching DinD 29.6.2 (issue #222)" ) assert "triex" not in content.replace("trixie", ""), ( "Dockerfile Docker APT repo must target the trixie suite (matches base image)" ) if __name__ == "__main__": import pytest pytest.main([__file__, "-v"])