Files
bot-bottle/tests/integration/test_gateway_image.py
T
didericis 2de223a33b
test / stage-firecracker-inputs (pull_request) Successful in 3s
test / integration-docker (pull_request) Successful in 10s
tracker-policy-pr / check-pr (pull_request) Successful in 12s
test / unit (pull_request) Successful in 1m31s
test / build-infra (pull_request) Successful in 3m45s
test / integration-firecracker (pull_request) Successful in 1m34s
test / coverage (pull_request) Successful in 1m59s
test / publish-infra (pull_request) Has been skipped
test / stage-firecracker-inputs (push) Successful in 3s
test / integration-docker (push) Successful in 16s
Update Quality Badges / update-badges (push) Failing after 42s
lint / lint (push) Successful in 53s
test / unit (push) Successful in 1m35s
test / build-infra (push) Successful in 3m30s
test / integration-firecracker (push) Successful in 1m46s
test / coverage (push) Successful in 2m1s
test / publish-infra (push) Successful in 2m14s
test(gateway-image): probe package imports, not pre-refactor flat modules
test_python_imports_supervise_module probed the image with
`import supervise; import supervise_server`, the flat sibling modules that
5ad3449 replaced. They now live in the installed package, so the test fails
with ModuleNotFoundError when actually run.

It went unnoticed because the whole class carries a GITEA_ACTIONS skip — it
is interactive-only, so CI never executes it.

All 4 tests in the class now pass against a freshly built gateway image.

Closes #429.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 13:40:14 -04:00

117 lines
4.2 KiB
Python

"""Integration: PRD 0024 chunk 1 — the gateway image builds
and the daemon binaries are present + executable inside it.
This test does NOT exercise the daemons running against real
config (routes.yaml, etc) — that lands in chunk 2 when the
renderer wires the bundle into compose. What we verify here is
the chunk-1 contract:
- Dockerfile.gateway builds (multi-stage works, base layers
pull, COPYs resolve).
- gitleaks, mitmdump are at the documented paths and answer
`--version`.
- The Python init at /app/gateway_init.py runs and prints the
expected "no daemons selected" line when the supervisor is
pointed at an empty daemon set.
Skips cleanly when docker is unavailable, or under act_runner
where the host bind-mount topology breaks multi-stage builds
that pull large bases.
"""
from __future__ import annotations
import os
import subprocess
import unittest
from tests._docker import skip_unless_docker
_IMAGE = "bot-bottle-gateway-test:chunk1"
_DOCKERFILE = "Dockerfile.gateway"
@skip_unless_docker()
@unittest.skipIf(
os.environ.get("GITEA_ACTIONS") == "true",
"skipped under act_runner: multi-stage build pulls a 200+MB "
"mitmproxy base + two upstream gateway images; runner storage "
"+ time budget make this an interactive-only test",
)
class TestGatewayImage(unittest.TestCase):
"""Builds the image once for the class, then runs a few
`docker run` probes against it."""
@classmethod
def setUpClass(cls) -> None:
repo_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
proc = subprocess.run(
["docker", "build", "-t", _IMAGE,
"-f", _DOCKERFILE, "."],
cwd=repo_root,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
if proc.returncode != 0:
raise unittest.SkipTest(
f"docker build failed; skipping image probes.\n"
f"{proc.stdout.decode('utf-8', errors='replace')[-2000:]}"
)
@classmethod
def tearDownClass(cls) -> None:
subprocess.run(
["docker", "image", "rm", "-f", _IMAGE],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
def _run_in_image(self, *cmd: str, timeout: float = 30.0) -> tuple[int, str]:
proc = subprocess.run(
["docker", "run", "--rm", "--entrypoint", cmd[0], _IMAGE,
*cmd[1:]],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
timeout=timeout,
)
return proc.returncode, proc.stdout.decode("utf-8", errors="replace")
def test_gitleaks_binary_present_and_versioned(self):
rc, out = self._run_in_image("/usr/bin/gitleaks", "version")
self.assertEqual(0, rc, msg=out)
# gitleaks prints a bare version string like "v8.x.y".
self.assertRegex(out, r"v?\d+\.\d+")
def test_mitmdump_binary_present_and_versioned(self):
rc, out = self._run_in_image("mitmdump", "--version")
self.assertEqual(0, rc, msg=out)
self.assertIn("Mitmproxy", out)
def test_python_imports_supervise_module(self):
# The supervise daemon is installed as part of the bot_bottle
# package (5ad3449), not as flat sibling modules under /app.
# Probe that the package imports resolve inside the image.
rc, out = self._run_in_image(
"python3", "-c",
"from bot_bottle import supervise, supervise_server; print('ok')",
)
self.assertEqual(0, rc, msg=out)
self.assertIn("ok", out)
def test_init_supervisor_runs_with_no_daemons(self):
# `nothing` matches no canonical daemon → supervisor exits 0
# immediately with the documented message. Confirms the
# ENTRYPOINT wiring works.
proc = subprocess.run(
["docker", "run", "--rm",
"-e", "BOT_BOTTLE_GATEWAY_DAEMONS=nothing",
_IMAGE],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
timeout=10.0,
)
out = proc.stdout.decode("utf-8", errors="replace")
self.assertEqual(0, proc.returncode, msg=out)
self.assertIn("no daemons selected", out)
if __name__ == "__main__":
unittest.main()