server-up/tests/test_containers.py
Ramon 3300d513a6
Some checks failed
Deploy server-up (dev) / deploy (push) Has been cancelled
v0.8.03-beta - mappen zonder compose-bestand verdwenen geruisloos
api_stacks sloeg elke map zonder compose-bestand over met een kale continue.
Zo'n map dook daarna nergens meer op: niet in de lijst, en de containers die
erbij horen belandden onder "handmatig gestart" omdat beheerd op datzelfde
compose-bestand afgaat.

De interface heeft er wel een weergave voor - rode stip, streepje in plaats van
een status, geen knoppen - die door dat continue nooit te zien kon zijn.

Ze staan er nu tussen met die waarschuwing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
2026-08-03 22:07:23 +02:00

151 lines
6.2 KiB
Python

"""Containers op de host: wat is beheerd en wat niet.
Het dashboard telt alles wat de Docker-daemon kent, terwijl de stacklijst alleen
toont wat in LIBRARY_DIR staat. Containers die daarbuiten vallen waren zichtbaar
in de cijfers maar nergens terug te vinden.
"""
import pytest
from conftest import login
PS_UITVOER = "\n".join([
"abc123\tvaultwarden\tvaultwarden/server:latest\trunning\tUp 2 hours\t0.0.0.0:8222->80/tcp\tvaultwarden\t/opt/serverup/stacks/vaultwarden",
"def456\tforgejo\tcodeberg.org/forgejo/forgejo:1.21\trunning\tUp 3 days\t0.0.0.0:3000->3000/tcp\tforgejo\t/opt/docker/forgejo",
"ghi789\tact_runner\tgitea/act_runner:latest\trunning\tUp 3 days\t\t\t",
"jkl012\tserver-up\tserver-up:0.5.46-beta\trunning\tUp 5 minutes\t127.0.0.1:5000->5000/tcp\tserver-up\t/opt/docker/server-up",
"mno345\toud-testje\tnginx:latest\texited\tExited (0) 4 days ago\t\t\t",
])
@pytest.fixture
def host(env, monkeypatch):
from core import docker
monkeypatch.setattr(docker, "_run", lambda *a, **k: type(
"R", (), {"returncode": 0, "stdout": PS_UITVOER, "stderr": ""})())
return docker
def test_list_containers_leest_alle_velden(host):
c = host.list_containers()
assert len(c) == 5
vw = next(x for x in c if x["name"] == "vaultwarden")
assert vw["image"] == "vaultwarden/server:latest"
assert vw["running"] is True
assert vw["project"] == "vaultwarden"
assert vw["working_dir"] == "/opt/serverup/stacks/vaultwarden"
def test_list_containers_zonder_labels(host):
"""Een container zonder compose-labels heeft lege project-/werkmapvelden."""
c = next(x for x in host.list_containers() if x["name"] == "act_runner")
assert c["project"] == "" and c["working_dir"] == ""
assert c["running"] is True
def test_gestopte_container_wordt_herkend(host):
c = next(x for x in host.list_containers() if x["name"] == "oud-testje")
assert c["running"] is False
def test_endpoint_scheidt_beheerd_van_de_rest(client, env, monkeypatch):
from core import docker
login(client)
# Eén stack in de library: vaultwarden.
stack = env["lib"] / "vaultwarden"
stack.mkdir(parents=True)
(stack / "docker-compose.yml").write_text("services:\n app:\n image: nginx\n")
monkeypatch.setattr(docker, "_run", lambda *a, **k: type(
"R", (), {"returncode": 0,
"stdout": PS_UITVOER.replace("/opt/serverup/stacks/vaultwarden",
str(stack)),
"stderr": ""})())
d = client.get("/api/containers").get_json()
beheerd = [c["name"] for c in d["containers"] if c["managed"]]
overig = [c["name"] for c in d["containers"] if not c["managed"]]
assert beheerd == ["vaultwarden"]
assert set(overig) == {"forgejo", "act_runner", "server-up", "oud-testje"}
assert d["managed"] == 1
assert d["total"] == 5
assert d["running"] == 4
def test_eigen_container_wordt_gemarkeerd(client, env, monkeypatch):
from core import docker
import app as app_module
login(client)
monkeypatch.setattr(app_module, "CONTAINER_NAME", "server-up")
monkeypatch.setattr(docker, "_run", lambda *a, **k: type(
"R", (), {"returncode": 0, "stdout": PS_UITVOER, "stderr": ""})())
d = client.get("/api/containers").get_json()
assert next(c for c in d["containers"] if c["name"] == "server-up")["self"] is True
def test_eigen_container_kan_niet_gestopt_worden(client, monkeypatch):
"""Zichzelf stoppen midden in een verzoek levert een halve actie op; daar
is de aparte herstartknop voor."""
import app as app_module
csrf = login(client)
monkeypatch.setattr(app_module, "CONTAINER_NAME", "server-up")
r = client.post("/api/containers/server-up/stop", headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
assert "herstarten" in r.get_json()["msg"]
def test_onbekende_container(client, host):
csrf = login(client)
r = client.post("/api/containers/bestaatniet/stop", headers={"X-CSRF-Token": csrf})
assert r.status_code == 404
def test_onbekende_actie(client, host):
csrf = login(client)
r = client.post("/api/containers/forgejo/verwijder", headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
def test_viewer_mag_geen_containers_bedienen(client, host):
csrf = login(client)
client.post("/api/auth/users",
json={"username": "kijker", "password": "eenlangwachtwoord",
"role": "viewer"},
headers={"X-CSRF-Token": csrf})
r = client.post("/api/auth/login",
json={"username": "kijker", "password": "eenlangwachtwoord"})
kijker_csrf = r.get_json()["csrf_token"]
assert client.get("/api/containers").status_code == 200
assert client.post("/api/containers/forgejo/stop",
headers={"X-CSRF-Token": kijker_csrf}).status_code == 403
# ── Een map zonder compose-bestand ───────────────────────────────────────────
# Die werd stilzwijgend overgeslagen: niet in de lijst, en de containers die
# erbij hoorden belandden onder "handmatig gestart". Je zag een leeg scherm en
# nergens stond waarom.
def test_map_zonder_compose_verdwijnt_niet_uit_de_lijst(client, env):
from conftest import login
login(client)
(env["lib"] / "kapotte-app").mkdir(parents=True, exist_ok=True)
d = client.get("/api/stacks").get_json()
namen = [s["name"] for s in d]
assert "kapotte-app" in namen, f"de map is niet zichtbaar: {namen}"
s = next(s for s in d if s["name"] == "kapotte-app")
assert s["has_compose"] is False, "hij doet alsof er compose is"
assert s["containers"] == []
assert s["running"] is False
def test_een_gewone_app_blijft_gewoon_staan(client, env, monkeypatch):
from conftest import login
from core import docker
monkeypatch.setattr(docker, "compose_ps", lambda *a, **k: [])
login(client)
d = env["lib"] / "echte-app"
d.mkdir(parents=True, exist_ok=True)
(d / "docker-compose.yml").write_text("services:\n web:\n image: nginx\n")
uit = client.get("/api/stacks").get_json()
s = next(s for s in uit if s["name"] == "echte-app")
assert s["has_compose"] is True