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_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>
117 lines
4.2 KiB
Python
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()
|