2026-03-12 11:33:16 +01:00
|
|
|
|
"""Shared pytest configuration for OpsBridge tests.
|
|
|
|
|
|
|
|
|
|
|
|
Registers capability and access_mode marks, and provides the
|
|
|
|
|
|
collect_capability_coverage() helper used by the cross-mode meta-test.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import textwrap
|
|
|
|
|
|
from typing import Iterable
|
|
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Shared fixtures
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
VALID_CONFIG = textwrap.dedent("""\
|
|
|
|
|
|
tunnels:
|
|
|
|
|
|
test-tunnel:
|
|
|
|
|
|
host: host.local
|
|
|
|
|
|
remote_port: 18000
|
|
|
|
|
|
local_port: 8000
|
|
|
|
|
|
ssh_user: ubuntu
|
|
|
|
|
|
ssh_key: ~/.ssh/id_ops
|
2026-05-15 09:38:29 +02:00
|
|
|
|
actor: adm-bernd
|
2026-03-12 11:33:16 +01:00
|
|
|
|
actors:
|
2026-05-15 09:38:29 +02:00
|
|
|
|
adm-bernd:
|
|
|
|
|
|
class: adm
|
2026-03-12 11:33:16 +01:00
|
|
|
|
description: Bernd
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
VALID_CONFIG_WITH_CATALOG = textwrap.dedent("""\
|
|
|
|
|
|
tunnels:
|
|
|
|
|
|
test-tunnel:
|
|
|
|
|
|
host: host.local
|
|
|
|
|
|
remote_port: 18000
|
|
|
|
|
|
local_port: 8000
|
|
|
|
|
|
ssh_user: ubuntu
|
|
|
|
|
|
ssh_key: ~/.ssh/id_ops
|
2026-05-15 09:38:29 +02:00
|
|
|
|
actor: adm-bernd
|
2026-03-12 11:33:16 +01:00
|
|
|
|
actors:
|
2026-05-15 09:38:29 +02:00
|
|
|
|
adm-bernd:
|
|
|
|
|
|
class: adm
|
2026-03-12 11:33:16 +01:00
|
|
|
|
description: Bernd
|
|
|
|
|
|
catalog_path: {catalog_path}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def config_file(tmp_path):
|
|
|
|
|
|
f = tmp_path / "tunnels.yaml"
|
|
|
|
|
|
f.write_text(VALID_CONFIG)
|
|
|
|
|
|
return f
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def state_dir(tmp_path):
|
|
|
|
|
|
d = tmp_path / "state"
|
|
|
|
|
|
d.mkdir()
|
|
|
|
|
|
return d
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def catalog_dir(tmp_path):
|
|
|
|
|
|
"""Minimal catalog directory with one domain, target, and bridge."""
|
|
|
|
|
|
cat = tmp_path / "catalog"
|
|
|
|
|
|
domain_dir = cat / "domains" / "coulombcore"
|
|
|
|
|
|
domain_dir.mkdir(parents=True)
|
|
|
|
|
|
(domain_dir / "domain.yaml").write_text(textwrap.dedent("""\
|
|
|
|
|
|
type: domain
|
|
|
|
|
|
id: coulombcore
|
|
|
|
|
|
name: CoulombCore Infrastructure
|
|
|
|
|
|
description: Core infrastructure domain
|
|
|
|
|
|
environment: production
|
|
|
|
|
|
"""))
|
|
|
|
|
|
targets_dir = domain_dir / "targets"
|
|
|
|
|
|
targets_dir.mkdir()
|
|
|
|
|
|
(targets_dir / "state-hub.yaml").write_text(textwrap.dedent("""\
|
|
|
|
|
|
type: target
|
|
|
|
|
|
id: state-hub
|
|
|
|
|
|
domain: coulombcore
|
|
|
|
|
|
kind: service
|
|
|
|
|
|
description: Infrastructure state coordination service
|
|
|
|
|
|
reachable_via:
|
|
|
|
|
|
- state-hub-coulombcore
|
|
|
|
|
|
"""))
|
|
|
|
|
|
bridges_dir = domain_dir / "bridges"
|
|
|
|
|
|
bridges_dir.mkdir()
|
|
|
|
|
|
(bridges_dir / "state-hub-coulombcore.yaml").write_text(textwrap.dedent("""\
|
|
|
|
|
|
type: bridge
|
|
|
|
|
|
id: state-hub-coulombcore
|
|
|
|
|
|
domain: coulombcore
|
|
|
|
|
|
target: state-hub
|
|
|
|
|
|
description: Bridge to state hub
|
|
|
|
|
|
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
|
|
|
|
|
|
reconnect:
|
|
|
|
|
|
max_attempts: 0
|
|
|
|
|
|
backoff_initial: 5
|
|
|
|
|
|
backoff_max: 60
|
|
|
|
|
|
"""))
|
|
|
|
|
|
actors_dir = cat / "actors"
|
|
|
|
|
|
actors_dir.mkdir()
|
|
|
|
|
|
(actors_dir / "agent.yaml").write_text(textwrap.dedent("""\
|
|
|
|
|
|
type: actor
|
|
|
|
|
|
id: agent.claude-coulombcore
|
|
|
|
|
|
class: automation
|
|
|
|
|
|
description: Claude Code agent on CoulombCore
|
|
|
|
|
|
"""))
|
|
|
|
|
|
return cat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def config_file_with_catalog(tmp_path, catalog_dir):
|
|
|
|
|
|
f = tmp_path / "tunnels.yaml"
|
|
|
|
|
|
f.write_text(VALID_CONFIG_WITH_CATALOG.format(catalog_path=str(catalog_dir)))
|
|
|
|
|
|
return f
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Coverage collector helper
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
def collect_capability_coverage(items: Iterable) -> set[tuple[str, str]]:
|
|
|
|
|
|
"""Walk pytest items and return set of (capability_name, access_mode) pairs.
|
|
|
|
|
|
|
|
|
|
|
|
Each test item is inspected for `capability` and `access_mode` markers.
|
|
|
|
|
|
A pair is added for every combination of capability × access_mode marks
|
|
|
|
|
|
found on a single item.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
items: Iterable of pytest.Item objects (from session.items or similar).
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Set of (capability_name, access_mode) tuples found across all items.
|
|
|
|
|
|
"""
|
|
|
|
|
|
covered: set[tuple[str, str]] = set()
|
|
|
|
|
|
for item in items:
|
|
|
|
|
|
capabilities = [
|
|
|
|
|
|
m.args[0] for m in item.iter_markers("capability") if m.args
|
|
|
|
|
|
]
|
|
|
|
|
|
modes = [
|
|
|
|
|
|
m.args[0] for m in item.iter_markers("access_mode") if m.args
|
|
|
|
|
|
]
|
|
|
|
|
|
for cap in capabilities:
|
|
|
|
|
|
for mode in modes:
|
|
|
|
|
|
covered.add((cap, mode))
|
|
|
|
|
|
return covered
|