Address Coderabbit issue

This commit is contained in:
Adam Outler
2025-11-01 23:54:54 +00:00
parent db5381db14
commit 23a0fac973
2 changed files with 138 additions and 45 deletions

View File

@@ -5,14 +5,17 @@ This set of tests requires netalertx-test image built and docker compose.
Ensure netalertx-test image is built prior to starting these tests. Ensure netalertx-test image is built prior to starting these tests.
''' '''
import copy
import os import os
import pathlib import pathlib
import re
import subprocess import subprocess
import pytest import pytest
import yaml import yaml
# Path to test configurations # Path to test configurations
CONFIG_DIR = pathlib.Path(__file__).parent / "configurations" CONFIG_DIR = pathlib.Path(__file__).parent / "configurations"
ANSI_ESCAPE = re.compile(r"\x1B\[[0-9;]*[A-Za-z]")
pytestmark = [pytest.mark.docker, pytest.mark.compose] pytestmark = [pytest.mark.docker, pytest.mark.compose]
@@ -70,16 +73,36 @@ COMPOSE_CONFIGS = {
"image": IMAGE, "image": IMAGE,
"network_mode": "host", "network_mode": "host",
"userns_mode": "host", "userns_mode": "host",
"read_only": True,
"cap_drop": ["ALL"],
"cap_add": ["NET_RAW", "NET_ADMIN", "NET_BIND_SERVICE"], "cap_add": ["NET_RAW", "NET_ADMIN", "NET_BIND_SERVICE"],
"user": "20211:20211", "user": "20211:20211",
"tmpfs": ["/tmp:mode=777"], "tmpfs": [
"/app/log:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime",
"/app/api:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,sync,noatime,nodiratime",
"/services/config/nginx/conf.active:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime",
"/services/run:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime",
"/tmp:uid=20211,gid=20211,mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime",
],
"volumes": [ "volumes": [
"./test_data/app_db:/app/db", {
"./test_data/app_config:/app/config", "type": "volume",
"./test_data/app_log:/app/log", "source": "__CONFIG_VOLUME__",
"./test_data/app_api:/app/api", "target": "/app/config",
"./test_data/nginx_conf:/services/config/nginx/conf.active", "read_only": False,
"./test_data/services_run:/services/run" },
{
"type": "volume",
"source": "__DB_VOLUME__",
"target": "/app/db",
"read_only": False,
},
{
"type": "bind",
"source": "/etc/localtime",
"target": "/etc/localtime",
"read_only": True,
},
], ],
"environment": { "environment": {
"TZ": "UTC" "TZ": "UTC"
@@ -88,21 +111,19 @@ COMPOSE_CONFIGS = {
} }
} }
} }
def _create_test_data_dirs(base_dir: pathlib.Path) -> None: def _create_test_data_dirs(base_dir: pathlib.Path) -> None:
"""Create test data directories with proper permissions.""" """Create test data directories and files with write permissions for the container user."""
dirs = ["app_db", "app_config", "app_log", "app_api", "nginx_conf", "services_run"] dirs = ["app_db", "app_config", "app_log", "app_api", "nginx_conf", "services_run"]
for dir_name in dirs: for dir_name in dirs:
dir_path = base_dir / "test_data" / dir_name dir_path = base_dir / "test_data" / dir_name
dir_path.mkdir(parents=True, exist_ok=True) dir_path.mkdir(parents=True, exist_ok=True)
dir_path.chmod(0o755) dir_path.chmod(0o777)
# Create basic config file # Create basic config file
config_file = base_dir / "test_data" / "app_config" / "app.conf" config_file = base_dir / "test_data" / "app_config" / "app.conf"
if not config_file.exists(): if not config_file.exists():
config_file.write_text("# Test configuration\n") config_file.write_text("# Test configuration\n")
config_file.chmod(0o666)
# Create basic db file # Create basic db file
db_file = base_dir / "test_data" / "app_db" / "app.db" db_file = base_dir / "test_data" / "app_db" / "app.db"
@@ -111,35 +132,79 @@ def _create_test_data_dirs(base_dir: pathlib.Path) -> None:
import sqlite3 import sqlite3
conn = sqlite3.connect(str(db_file)) conn = sqlite3.connect(str(db_file))
conn.close() conn.close()
db_file.chmod(0o666)
def _run_docker_compose(compose_file: pathlib.Path, project_name: str, timeout: int = 30, env_vars: dict | None = None) -> subprocess.CompletedProcess: def _run_docker_compose(
compose_file: pathlib.Path,
project_name: str,
timeout: int = 5,
env_vars: dict | None = None,
detached: bool = False,
) -> subprocess.CompletedProcess:
"""Run docker compose up and capture output.""" """Run docker compose up and capture output."""
cmd = [ cmd = [
"docker", "compose", "docker", "compose",
"-f", str(compose_file), "-f", str(compose_file),
"-p", project_name, "-p", project_name,
"up",
"--abort-on-container-exit",
"--timeout", str(timeout)
] ]
up_cmd = cmd + ["up"]
if detached:
up_cmd.append("-d")
else:
up_cmd.extend([
"--abort-on-container-exit",
"--timeout", str(timeout)
])
# Merge custom env vars with current environment # Merge custom env vars with current environment
env = os.environ.copy() env = os.environ.copy()
if env_vars: if env_vars:
env.update(env_vars) env.update(env_vars)
try: try:
result = subprocess.run( if detached:
cmd, up_result = subprocess.run(
cwd=compose_file.parent, up_cmd,
stdout=subprocess.PIPE, cwd=compose_file.parent,
stderr=subprocess.PIPE, stdout=subprocess.PIPE,
text=True, stderr=subprocess.PIPE,
timeout=timeout + 10, text=True,
check=False, timeout=timeout,
env=env, check=False,
) env=env,
)
logs_cmd = cmd + ["logs"]
logs_result = subprocess.run(
logs_cmd,
cwd=compose_file.parent,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=timeout,
check=False,
env=env,
)
result = subprocess.CompletedProcess(
up_cmd,
up_result.returncode,
stdout=(up_result.stdout or "") + (logs_result.stdout or ""),
stderr=(up_result.stderr or "") + (logs_result.stderr or ""),
)
else:
result = subprocess.run(
up_cmd,
cwd=compose_file.parent,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=timeout + 10,
check=False,
env=env,
)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
# Clean up on timeout # Clean up on timeout
subprocess.run(["docker", "compose", "-f", str(compose_file), "-p", project_name, "down", "-v"], subprocess.run(["docker", "compose", "-f", str(compose_file), "-p", project_name, "down", "-v"],
@@ -152,6 +217,19 @@ def _run_docker_compose(compose_file: pathlib.Path, project_name: str, timeout:
# Combine stdout and stderr # Combine stdout and stderr
result.output = result.stdout + result.stderr result.output = result.stdout + result.stderr
# Surface command context and IO for any caller to aid debugging
print("\n[compose command]", " ".join(up_cmd))
print("[compose cwd]", str(compose_file.parent))
print("[compose stdin]", "<none>")
if result.stdout:
print("[compose stdout]\n" + result.stdout)
if result.stderr:
print("[compose stderr]\n" + result.stderr)
if detached:
logs_cmd_display = cmd + ["logs"]
print("[compose logs command]", " ".join(logs_cmd_display))
return result return result
@@ -220,32 +298,47 @@ def test_normal_startup_no_warnings_compose(tmp_path: pathlib.Path) -> None:
base_dir = tmp_path / "normal_startup" base_dir = tmp_path / "normal_startup"
base_dir.mkdir() base_dir.mkdir()
# Create test data directories with proper permissions project_name = "netalertx-normal"
_create_test_data_dirs(base_dir)
# Make sure directories are writable by netalertx user # Create compose file mirroring production docker-compose.yml
for dir_name in ["app_db", "app_config", "app_log", "app_api", "nginx_conf", "services_run"]: compose_config = copy.deepcopy(COMPOSE_CONFIGS["normal_startup"])
dir_path = base_dir / "test_data" / dir_name service = compose_config["services"]["netalertx"]
dir_path.chmod(0o777) # Allow all users to write
config_volume_name = f"{project_name}_config"
db_volume_name = f"{project_name}_db"
service["volumes"][0]["source"] = config_volume_name
service["volumes"][1]["source"] = db_volume_name
service.setdefault("environment", {})
service["environment"].update({
"PORT": "22111",
"GRAPHQL_PORT": "22112",
})
compose_config["volumes"] = {
config_volume_name: {},
db_volume_name: {},
}
# Create compose file
compose_config = COMPOSE_CONFIGS["normal_startup"].copy()
compose_file = base_dir / "docker-compose.yml" compose_file = base_dir / "docker-compose.yml"
with open(compose_file, 'w') as f: with open(compose_file, 'w') as f:
yaml.dump(compose_config, f) yaml.dump(compose_config, f)
# Run docker compose # Run docker compose
result = _run_docker_compose(compose_file, "netalertx-normal") result = _run_docker_compose(compose_file, project_name, detached=True)
# Check that expected warnings with pipe characters appear clean_output = ANSI_ESCAPE.sub("", result.output)
# These are the typical warnings that appear in a "normal" startup
assert "⚠️ Warning: Excessive capabilities detected" in result.output
assert "⚠️ Warning: Container is running as read-write" in result.output
assert "═══" in result.output # Box drawing characters in warnings
# Should not have critical permission errors (these indicate test setup issues) # Check that startup completed without critical issues and mounts table shows success
assert "Write permission denied" not in result.output assert "Startup pre-checks" in clean_output
assert "CRITICAL" in result.output # CRITICAL messages are expected when permissions fail assert "" not in clean_output
assert "/app/db | ✅" in clean_output
# Ensure no critical errors or permission problems surfaced
assert "Write permission denied" not in clean_output
assert "CRITICAL" not in clean_output
assert "⚠️" not in clean_output
def test_ram_disk_mount_analysis_compose(tmp_path: pathlib.Path) -> None: def test_ram_disk_mount_analysis_compose(tmp_path: pathlib.Path) -> None:

View File

@@ -119,8 +119,8 @@ def _run_container(
cmd.extend(["-v", mount]) cmd.extend(["-v", mount])
# Copy the script content and run it # Copy the script content and run it
script_path = "install/production-filesystem/entrypoint.d/99-ports-available.sh" script_path = pathlib.Path("install/production-filesystem/entrypoint.d/99-ports-available.sh")
with open(script_path, 'r') as f: with script_path.open('r', encoding='utf-8') as f:
script_content = f.read() script_content = f.read()
# Use printf to avoid shell interpretation issues # Use printf to avoid shell interpretation issues