Files
ops-bridge/tests/test_catalog_loader.py
Bernd Worsch 91d031ae20 feat: implement OpsCatalog extension (BRIDGE-WP-0002)
Adds the OpsCatalog subsystem: a Git-backed YAML catalog of operations
domains, targets, bridges, and actor classes. Includes catalog loader,
cross-reference validator, bridge resolver (inline-first, catalog
fallback), and new CLI commands: `bridge targets`, `bridge targets show`,
`bridge catalog list/validate/show`. Updates `up/down/restart` to resolve
bridge names from the catalog when not defined inline. 142 tests, all green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 02:05:06 +00:00

142 lines
4.6 KiB
Python

"""Tests for catalog loader."""
import textwrap
from pathlib import Path
import pytest
from bridge.catalog.loader import CatalogLoadError, load_catalog
from bridge.catalog.models import Catalog
@pytest.fixture
def catalog_dir(tmp_path):
"""Build a minimal valid catalog fixture."""
root = tmp_path / "opscatalog"
domain_dir = root / "domains" / "coulombcore"
(domain_dir / "targets").mkdir(parents=True)
(domain_dir / "bridges").mkdir(parents=True)
(domain_dir / "docs").mkdir(parents=True)
actors_dir = root / "actors"
actors_dir.mkdir(parents=True)
(domain_dir / "domain.yaml").write_text(textwrap.dedent("""\
type: domain
id: coulombcore
name: CoulombCore Infrastructure
description: Core infra
environment: production
"""))
(domain_dir / "targets" / "state-hub.yaml").write_text(textwrap.dedent("""\
type: target
id: state-hub
domain: coulombcore
kind: service
description: State coordination service
reachable_via:
- state-hub-coulombcore
"""))
(domain_dir / "bridges" / "state-hub-coulombcore.yaml").write_text(textwrap.dedent("""\
type: bridge
id: state-hub-coulombcore
domain: coulombcore
target: state-hub
description: Ops bridge
access_method: ssh-reverse
host: coulombcore.local
remote_port: 18000
local_port: 8000
ssh_user: ubuntu
ssh_key: ~/.ssh/id_ops
actor: agent.claude-coulombcore
health_check:
url: http://127.0.0.1:18000/health
interval_seconds: 30
timeout_seconds: 5
reconnect:
max_attempts: 0
backoff_initial: 5
backoff_max: 60
"""))
(actors_dir / "agents.yaml").write_text(textwrap.dedent("""\
type: actor
id: agent.claude-coulombcore
class: automation
description: Claude Code agent on CoulombCore
"""))
(domain_dir / "docs" / "overview.md").write_text("# Overview\nSome ops notes.")
return root
class TestLoadCatalog:
def test_loads_domain(self, catalog_dir):
cat = load_catalog(catalog_dir)
assert "coulombcore" in cat.domains
d = cat.domains["coulombcore"]
assert d.name == "CoulombCore Infrastructure"
assert d.environment == "production"
def test_loads_target(self, catalog_dir):
cat = load_catalog(catalog_dir)
assert "state-hub" in cat.targets
t = cat.targets["state-hub"]
assert t.domain == "coulombcore"
assert t.kind == "service"
assert "state-hub-coulombcore" in t.reachable_via
def test_loads_bridge(self, catalog_dir):
cat = load_catalog(catalog_dir)
assert "state-hub-coulombcore" in cat.bridges
b = cat.bridges["state-hub-coulombcore"]
assert b.host == "coulombcore.local"
assert b.remote_port == 18000
assert b.health_check is not None
assert b.health_check.url == "http://127.0.0.1:18000/health"
assert b.reconnect is not None
assert b.reconnect.max_attempts == 0
def test_loads_actor(self, catalog_dir):
cat = load_catalog(catalog_dir)
assert "agent.claude-coulombcore" in cat.actors
a = cat.actors["agent.claude-coulombcore"]
assert a.actor_class == "automation"
def test_unknown_type_skipped(self, catalog_dir):
(catalog_dir / "domains" / "coulombcore" / "unknown.yaml").write_text(
"type: mystery\nid: x\n"
)
# Should not raise
cat = load_catalog(catalog_dir)
assert isinstance(cat, Catalog)
def test_empty_catalog_dir(self, tmp_path):
root = tmp_path / "empty"
root.mkdir()
cat = load_catalog(root)
assert cat.domains == {}
assert cat.bridges == {}
def test_missing_required_field_raises(self, tmp_path):
root = tmp_path / "bad"
domain_dir = root / "domains" / "x"
domain_dir.mkdir(parents=True)
(domain_dir / "domain.yaml").write_text("type: domain\nname: X\n")
with pytest.raises(CatalogLoadError, match="id"):
load_catalog(root)
def test_nonexistent_path_raises(self, tmp_path):
with pytest.raises(CatalogLoadError, match="not found"):
load_catalog(tmp_path / "nonexistent")
def test_invalid_yaml_raises(self, tmp_path):
root = tmp_path / "bad"
domain_dir = root / "domains" / "x"
domain_dir.mkdir(parents=True)
(domain_dir / "domain.yaml").write_text("type: domain\n[\nbad: yaml")
with pytest.raises(CatalogLoadError):
load_catalog(root)