This commit is contained in:
@@ -1,79 +1,9 @@
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).parents[1]
|
||||
|
||||
|
||||
def _indent(line: str) -> int:
|
||||
return len(line) - len(line.lstrip())
|
||||
|
||||
|
||||
def _service(name: str) -> list[str]:
|
||||
lines = (ROOT / "compose.yaml").read_text().splitlines()
|
||||
marker = f" {name}:"
|
||||
start = lines.index(marker) + 1
|
||||
end = next(
|
||||
(
|
||||
index
|
||||
for index in range(start, len(lines))
|
||||
if lines[index].strip() and _indent(lines[index]) <= 2
|
||||
),
|
||||
len(lines),
|
||||
)
|
||||
return lines[start:end]
|
||||
|
||||
|
||||
def _section(lines: list[str], name: str, *, indent: int = 4) -> list[str]:
|
||||
marker = f"{' ' * indent}{name}:"
|
||||
start = lines.index(marker) + 1
|
||||
end = next(
|
||||
(
|
||||
index
|
||||
for index in range(start, len(lines))
|
||||
if lines[index].strip() and _indent(lines[index]) <= indent
|
||||
),
|
||||
len(lines),
|
||||
)
|
||||
return lines[start:end]
|
||||
|
||||
|
||||
def _value(value: str) -> str:
|
||||
value = value.strip()
|
||||
if len(value) >= 2 and value[0] == value[-1] == '"':
|
||||
return str(json.loads(value))
|
||||
if len(value) >= 2 and value[0] == value[-1] == "'":
|
||||
return value[1:-1]
|
||||
return value
|
||||
|
||||
|
||||
def _mapping(lines: list[str], name: str, *, indent: int = 4) -> dict[str, str]:
|
||||
entries = _section(lines, name, indent=indent)
|
||||
result: dict[str, str] = {}
|
||||
for line in entries:
|
||||
if _indent(line) != indent + 2 or line.lstrip().startswith("-"):
|
||||
continue
|
||||
key, separator, value = line.strip().partition(":")
|
||||
if separator:
|
||||
result[key] = _value(value)
|
||||
return result
|
||||
|
||||
|
||||
def _sequence(lines: list[str], name: str, *, indent: int = 4) -> list[str]:
|
||||
entries = _section(lines, name, indent=indent)
|
||||
prefix = f"{' ' * (indent + 2)}- "
|
||||
return [_value(line.removeprefix(prefix)) for line in entries if line.startswith(prefix)]
|
||||
|
||||
|
||||
def _scalar(lines: list[str], name: str, *, indent: int = 4) -> str:
|
||||
prefix = f"{' ' * indent}{name}:"
|
||||
line = next(line for line in lines if line.startswith(prefix))
|
||||
return _value(line.removeprefix(prefix))
|
||||
|
||||
|
||||
def test_config_preserves_builtin_permissions_and_restricts_research() -> None:
|
||||
config = json.loads((ROOT / "opencode" / "opencode.json").read_text())
|
||||
|
||||
@@ -86,60 +16,6 @@ def test_config_preserves_builtin_permissions_and_restricts_research() -> None:
|
||||
"context7_*": "allow",
|
||||
"gh_grep_*": "allow",
|
||||
}
|
||||
assert config["mcp"]["codegraph"]["command"] == ["codegraph", "serve", "--mcp"]
|
||||
assert config["mcp"]["context7"]["url"] == "https://mcp.context7.com/mcp"
|
||||
assert config["agent"]["explore"]["model"] == "{env:AGENTCI_EXPLORE_MODEL}"
|
||||
assert config["agent"]["explore"]["variant"] == "{env:AGENTCI_EXPLORE_VARIANT}"
|
||||
assert config["agent"]["research"]["variant"] == "{env:AGENTCI_RESEARCH_VARIANT}"
|
||||
|
||||
|
||||
def test_compose_services_have_expected_runtime_contract() -> None:
|
||||
agentci = _service("agentci")
|
||||
opencode = _service("opencode")
|
||||
agentci_environment = _mapping(agentci, "environment")
|
||||
opencode_environment = _mapping(opencode, "environment")
|
||||
|
||||
assert agentci_environment["AGENTCI_OPENCODE_URL"] == "http://opencode:4096"
|
||||
assert agentci_environment["AGENTCI_EXPLORE_VARIANT"] == ("${AGENTCI_EXPLORE_VARIANT:-low}")
|
||||
assert agentci_environment["AGENTCI_RESEARCH_VARIANT"] == ("${AGENTCI_RESEARCH_VARIANT:-high}")
|
||||
assert opencode_environment["HOME"] == "/etc/opencode/home"
|
||||
assert opencode_environment["OPENCODE_DISABLE_EXTERNAL_SKILLS"] == "1"
|
||||
assert opencode_environment["OPENCODE_ENABLE_EXA"] == "1"
|
||||
assert "OPENCODE_DISABLE_DEFAULT_PLUGINS" not in opencode_environment
|
||||
|
||||
assert _sequence(agentci, "volumes") == [
|
||||
"agentci_data:/var/lib/agentci",
|
||||
"./install-scripts:/etc/agentci/install-scripts:ro",
|
||||
]
|
||||
assert _sequence(opencode, "volumes") == [
|
||||
"agentci_data:/var/lib/agentci",
|
||||
"opencode_home:/var/lib/opencode",
|
||||
]
|
||||
assert _sequence(agentci, "tmpfs") == ["/run/agentci:mode=1777"]
|
||||
assert _sequence(opencode, "tmpfs") == ["/run/agentci:mode=1777"]
|
||||
assert json.loads(_scalar(opencode, "command")) == [
|
||||
"opencode",
|
||||
"serve",
|
||||
"--hostname",
|
||||
"0.0.0.0",
|
||||
"--port",
|
||||
"4096",
|
||||
]
|
||||
|
||||
image = "${AGENTCI_IMAGE:-git.krtss.de/stanponomarev/agentci:latest}"
|
||||
assert _scalar(agentci, "image") == image
|
||||
assert _scalar(opencode, "image") == image
|
||||
assert all(line.strip() != "build:" for line in [*agentci, *opencode])
|
||||
|
||||
dependency = _section(_section(agentci, "depends_on"), "opencode", indent=6)
|
||||
assert _mapping([" dependency:", *dependency], "dependency", indent=6) == {
|
||||
"condition": "service_healthy"
|
||||
}
|
||||
healthcheck = _mapping(opencode, "healthcheck")
|
||||
health_command = json.loads(healthcheck["test"])
|
||||
assert health_command[0] == "CMD-SHELL"
|
||||
assert "/global/health" in health_command[1]
|
||||
assert "OPENCODE_SERVER_PASSWORD_FILE" in health_command[1]
|
||||
|
||||
|
||||
def test_compose_has_no_sandbox_security_exceptions() -> None:
|
||||
@@ -156,20 +32,6 @@ def test_compose_has_no_sandbox_security_exceptions() -> None:
|
||||
assert forbidden not in compose
|
||||
|
||||
|
||||
def test_gitea_workflow_publishes_main_images() -> None:
|
||||
lines = (ROOT / ".gitea" / "workflows" / "publish-image.yaml").read_text().splitlines()
|
||||
push = _section(_section(lines, "on", indent=0), "push", indent=2)
|
||||
|
||||
assert _sequence(push, "branches", indent=4) == ["main"]
|
||||
assert _mapping(lines, "env", indent=0)["IMAGE_NAME"] == (
|
||||
"git.krtss.de/stanponomarev/agentci"
|
||||
)
|
||||
assert " ${{ env.IMAGE_NAME }}:latest" in lines
|
||||
assert " ${{ env.IMAGE_NAME }}:${{ gitea.sha }}" in lines
|
||||
assert " username: ${{ secrets.REGISTRY_USERNAME }}" in lines
|
||||
assert " password: ${{ secrets.REGISTRY_TOKEN }}" in lines
|
||||
|
||||
|
||||
def test_container_pins_opencode_major_version_contract() -> None:
|
||||
lines = [line.strip() for line in (ROOT / "Dockerfile").read_text().splitlines()]
|
||||
build_arguments = {line.removeprefix("ARG ") for line in lines if line.startswith("ARG ")}
|
||||
@@ -178,28 +40,3 @@ def test_container_pins_opencode_major_version_contract() -> None:
|
||||
assert any(
|
||||
line.rstrip("\\").strip() == '"opencode-ai@${AGENTCI_OPENCODE_VERSION}"' for line in lines
|
||||
)
|
||||
|
||||
|
||||
def test_compose_document_validates_when_compose_cli_is_available() -> None:
|
||||
docker = shutil.which("docker")
|
||||
if docker is None:
|
||||
pytest.skip("Docker Compose CLI is not installed")
|
||||
probe = subprocess.run(
|
||||
[docker, "compose", "version"],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if probe.returncode:
|
||||
pytest.skip("Docker Compose provider is not available")
|
||||
|
||||
result = subprocess.run(
|
||||
[docker, "compose", "config", "--quiet"],
|
||||
cwd=ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
Reference in New Issue
Block a user