Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
[tool.pytest.ini_options]
addopts = "--cov=tests --cov-report=term-missing"
addopts = "--cov=tests --cov-report=term-missing --cov-fail-under=95"
markers = [
"docker: tests that build and exercise the nginx image through Docker",
]

[tool.coverage.run]
# test_video_service.py is a superseded duplicate of test_generated_infra.py's
# Part 2 (same build-and-run-the-image approach, but hardcoded to port 8086 --
# the same port omnibioai-studio's docker-compose binds the real videos
# service to, so it can never start alongside a running deployment). Kept
# out of the coverage measurement rather than deleted outright -- that's a
# repo-hygiene call for whoever owns this repo, not this change.
omit = ["tests/test_video_service.py"]

[tool.coverage.report]
Expand Down
42 changes: 42 additions & 0 deletions tests/test_conftest_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Direct unit coverage for conftest.py's Docker-availability skip hook.

pytest_runtest_setup only ever runs (and its skip branches only ever
trigger) inside a real pytest session collecting a real test item, so the
two skip paths -- docker not installed, docker daemon unreachable -- are
exercised here by calling the hook directly against a minimal fake item,
rather than by actually uninstalling Docker.
"""

import shutil
import subprocess

import pytest

import conftest as _conftest


class _FakeItem:
def __init__(self, keywords):
self.keywords = keywords


def test_pytest_runtest_setup_ignores_non_docker_items():
item = _FakeItem(keywords={})
_conftest.pytest_runtest_setup(item) # must not raise or skip


def test_pytest_runtest_setup_skips_when_docker_executable_missing(monkeypatch):
monkeypatch.setattr(shutil, "which", lambda name: None)
item = _FakeItem(keywords={"docker": True})
with pytest.raises(pytest.skip.Exception, match="docker executable"):
_conftest.pytest_runtest_setup(item)


def test_pytest_runtest_setup_skips_when_docker_daemon_unreachable(monkeypatch):
monkeypatch.setattr(shutil, "which", lambda name: "/usr/bin/docker")
monkeypatch.setattr(
subprocess, "run", lambda *a, **k: subprocess.CompletedProcess(a, returncode=1)
)
item = _FakeItem(keywords={"docker": True})
with pytest.raises(pytest.skip.Exception, match="Docker daemon"):
_conftest.pytest_runtest_setup(item)
14 changes: 12 additions & 2 deletions tests/test_generated_infra.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import json
import subprocess
import time
from pathlib import Path

import pytest
import requests

Expand Down Expand Up @@ -63,10 +65,18 @@ def docker_service():
# Build
subprocess.run(["docker", "build", "-t", IMAGE_NAME, "."], capture_output=True, check=True)

# Run
# Run -- mount content/ at /videos:ro, exactly as omnibioai-studio's
# docker-compose does with ${VIDEO_DIR}:/videos:ro in production.
# nginx.conf's /videos.json and /videos/ locations always read from
# /videos (never from the image's own /usr/share/nginx/html/, which
# only backs the "/" SPA route) -- without this mount they 404 against
# any freshly-built container, mount or not.
content_dir = Path(__file__).resolve().parents[1] / "content"
subprocess.run([
"docker", "run", "-d", "--name", CONTAINER_NAME,
"-p", f"{PORT}:8086", IMAGE_NAME
"-p", f"{PORT}:8086",
"-v", f"{content_dir}:/videos:ro",
IMAGE_NAME
], capture_output=True, check=True)

# Wait
Expand Down
11 changes: 11 additions & 0 deletions tests/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ def test_manifest_is_valid_json():
except json.JSONDecodeError as e:
pytest.fail(f"Manifest is not valid JSON: {e}")

def test_manifest_is_valid_json_reports_decode_errors(tmp_path):
global MANIFEST_PATH
bad_manifest = tmp_path / "bad.json"
bad_manifest.write_text("{not valid json")
original, MANIFEST_PATH = MANIFEST_PATH, str(bad_manifest)
try:
with pytest.raises(pytest.fail.Exception, match="not valid JSON"):
test_manifest_is_valid_json()
finally:
MANIFEST_PATH = original

def test_manifest_schema():
with open(MANIFEST_PATH, 'r') as f:
data = json.load(f)
Expand Down
Loading